Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Angular, how to pass JSON object/array into directive?

Currently, my app has a controller that takes in a JSON file then iterates through them using "ng-repeat". This is all working great, but I also have a directive that needs to iterate through the same JSON file. This is posing a problem as I cannot request the same JSON file twice on one page (nor would I want to because it would be inefficient). Both the directive and controller request and iterate through the JSON data just fine if I change the filename of one of the JSON files.

What I'm wondering is: what's the best way to go about passing the array formed from my controller's JSON request into the directive? How can I pass the array into my directive and iterate through when I've already accessed it via my controller?

Controller

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 (Works when I use a file name besides locations.json, since I've already requested it once

.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    }); }); 
like image 393
Omegalen Avatar asked Feb 10 '14 01:02

Omegalen


People also ask

How do you pass JSON data into an array?

Approach 1: First convert the JSON string to the JavaScript object using JSON. Parse() method and then take out the values of the object and push them into the array using push() method.

Can you pass arrays in JSON?

Solution: In order to pass the array value to the post action, you need to serialize the array value and pass the serialized data to the mapper action. In the post action, you can de-serialize the serialized data and use the data based on your requirement.

Can a JSON object contain an array?

In JSON, array values must be of type string, number, object, array, boolean or null. In JavaScript, array values can be all of the above, plus any other valid JavaScript expression, including functions, dates, and undefined.


2 Answers

If you want to follow all the "best practices," there's a few things I'd recommend, some of which are touched on in other answers and comments to this question.


First, while it doesn't have too much of an affect on the specific question you asked, you did mention efficiency, and the best way to handle shared data in your application is to factor it out into a service.

I would personally recommend embracing AngularJS's promise system, which will make your asynchronous services more composable compared to raw callbacks. Luckily, Angular's $http service already uses them under the hood. Here's a service that will return a promise that resolves to the data from the JSON file; calling the service more than once will not cause a second HTTP request.

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

As far as getting the data into your directive, it's important to remember that directives are designed to abstract generic DOM manipulation; you should not inject them with application-specific services. In this case, it would be tempting to simply inject the locations service into the directive, but this couples the directive to that service.

A brief aside on code modularity: a directive’s functions should almost never be responsible for getting or formatting their own data. There’s nothing to stop you from using the $http service from within a directive, but this is almost always the wrong thing to do. Writing a controller to use $http is the right way to do it. A directive already touches a DOM element, which is a very complex object and is difficult to stub out for testing. Adding network I/O to the mix makes your code that much more difficult to understand and that much more difficult to test. In addition, network I/O locks in the way that your directive will get its data – maybe in some other place you’ll want to have this directive receive data from a socket or take in preloaded data. Your directive should either take data in as an attribute through scope.$eval and/or have a controller to handle acquiring and storing the data.

- The 80/20 Guide to Writing AngularJS Directives

In this specific case, you should place the appropriate data on your controller's scope and share it with the directive via an attribute.

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

In this way, the map directive can be used with any set of location data--the directive is not hard-coded to use a specific set of data, and simply linking the directive by including it in the DOM will not fire off random HTTP requests.

like image 180
Michelle Tilley Avatar answered Sep 21 '22 23:09

Michelle Tilley


As you say, you don't need to request the file twice. Pass it from your controller to your directive. Assuming you use the directive inside the scope of the controller:

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

Then in your HTML (where you call upon the directive).
Note: locations is a reference to your controllers $scope.locations.

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

And finally in your directive

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

This is just an outline to point you in the right direction, so it's incomplete and not tested.

like image 31
Index Avatar answered Sep 22 '22 23:09

Index