If you're aiming to adhere to all the "best practices," there are some recommendations I'd like to share, many of which have been touched upon in other responses and comments related to this topic.
First and foremost, although it may not directly impact the question at hand, considering efficiency, the optimal approach to managing shared data in your application is to extract it into a service.
I personally suggest embracing AngularJS's promise system, which enhances the composability of your asynchronous services compared to using raw callbacks. Fortunately, Angular's $http
service already incorporates promises internally. Here's a service that delivers a promise resolving to data from a JSON file; making multiple calls to the service won't trigger additional HTTP requests.
app.factory('locations', function($http) {
var promise = null;
return function() {
if (promise) {
// If the data has been retrieved before,
// return the existing promise.
return promise;
} else {
promise = $http.get('locations/locations.json');
return promise;
}
};
});
When it comes to integrating data into your directive, it's crucial to remember that directives are meant to abstract generic DOM manipulation; thus, injecting them with application-specific services is discouraged. While it might be tempting to inject the locations
service into the directive, this creates a tight coupling between the directive and the service.
A quick note on code modularity: a directive's functions should typically not handle data retrieval or formatting. While using the $http service within a directive is feasible, it's often not the recommended approach. Employing a controller for $http operations is more appropriate. Directives already interact with complex DOM elements, which are challenging to stub for testing. Introducing network operations complicates the code and testing process significantly. Furthermore, binding your directive to network I/O limits its flexibility in terms of data retrieval methods. As your application evolves, you may want the directive to retrieve data from a socket or preloaded source. Your directive should either accept data through attributes using scope.$eval and/or utilize a controller to manage data acquisition and storage.
- The 80/20 Guide to Writing AngularJS Directives
In this specific scenario, it's advisable to place the necessary data on your controller's scope and pass it to 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) {
// perform actions
});
});
}
};
});
This approach allows the map
directive to work with any dataset of location information—without being hardcoded to a specific data source—and simply including the directive in the DOM won't trigger unnecessary HTTP requests.