52

В настоящее время у моего приложения есть контроллер, который принимает файл JSON, а затем выполняет итерацию через них с помощью «ng-repeat». Все это отлично работает, но у меня также есть директива, которая должна проходить через один и тот же файл JSON. Это создает проблему, поскольку я не могу дважды запрашивать один и тот же файл JSON на одной странице (и не хочу, потому что это будет неэффективно). И запрос директивы и контроллера, и итерация через данные JSON просто прекрасны, если я изменю имя файла одного из файлов JSON.В Угловом, как передать объект/массив JSON в директиву?

Что мне интересно: как лучше всего передать массив, сформированный из запроса JSON моего контроллера, в директиву? Как я могу передать массив в мою директиву и пропустить, когда я уже обращался к нему через мой контроллер?

Контроллер

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 

Директива (работает, когда я использую имя файла, кроме locations.json, так как я уже просил его однажды

.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 
    }); 
}); 
+2

Прежде всего, вы должны использовать службу вместо использования контроллера для получения данных о ваших местоположениях. После этого вы получаете зависимость от ваших данных с вашей службы как в контроллере, так и в вашей директиве. –

ответ

91

Если вы хотите следовать все «лучшие практики», есть несколько вещей, которые я рекомендовал бы, некоторые из которые затронуты в других ответах и ​​комментариях к этому вопросу.


Во-первых, в то время как он не имеет слишком много аффекта на конкретный вопрос, который Вы задали, вы действительно упоминала эффективность, и лучший способ для обработки общих данных в вашем приложении к фактору это в оказание услуг.

Я лично рекомендовал бы охватить AngularJS promise system, что сделает ваши асинхронные службы более сложными по сравнению с необработанными обратными вызовами. К счастью, сервис Angular's $http уже использует их под капотом. Вот сервис, который вернет обещание, которое решает данные из файла JSON; вызов службы более одного раза не вызовет второй 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; 
    } 
    }; 
}); 

Насколько получать данные в вашу директиву, важно помнить, что директивы предназначены для абстрактного общего манипулирования DOM; вы должны указать , а не. В этом случае было бы заманчиво просто ввести в директиву службу locations, но это сопрягает директиву с этой услугой.

Краткая информация о модульности кода: функции директивы должны почти никогда не отвечать за получение или форматирование собственных данных. Нет ничего, что помешало бы вам использовать службу $ http из директивы, но это почти всегда неправильно. Написание контроллера для использования $ http - это правильный способ сделать это. Директива уже касается элемента DOM, который является очень сложным объектом и его трудно вырезать для тестирования. Добавление сетевого ввода-вывода в микс делает ваш код намного сложнее для понимания и гораздо труднее проверить. Кроме того, сетевые операции ввода-вывода блокируются так, что ваша директива будет получать свои данные - возможно, в каком-то другом месте вы захотите, чтобы эта директива получала данные из сокета или принимала предварительно загруженные данные. Ваша директива должна либо принимать данные в качестве атрибута через область действия. $ Eval и/или иметь контроллер для обработки и хранения данных.

- The 80/20 Guide to Writing AngularJS Directives

В данном конкретном случае, вы должны поместить соответствующие данные на сферу вашего контроллера и делиться ею с директивой через атрибут.

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

В Таким образом, map директивы может использоваться с любым набором данных о местоположении - директива не жестко, чтобы использовать определенный набор данных, а просто связывая директиву, включив его в DOM, не будет удалять случайные HTTP-запросы.

+0

Я выбрал это как лучший ответ, потому что он был наиболее полным. Спасибо Брэндон. Прежде всего я хотел бы обратить внимание на это: мне пришлось зарегистрировать обратный вызов слушателя ($ watch) функции foreach в директиве. Прежде чем я это сделал, директива не регистрировала область scope.boulders как определенный объект, но когда я проверил объект scope, я бы увидел, что массив сохранен. Я узнал, что это происходит потому, что обещание выполняется асинхронно - это означает, что моя директива запускается до того, как она сможет зарегистрировать изменение в области видимости. – Omegalen

+0

Нет проблем. Ваша «часовая» проблема звучит мне смешно; если вам интересно, я с удовольствием посмотрю - вы можете найти мою контактную информацию в моем профиле SO. –

+0

@Omegalen Подумав об этом, вы, конечно, правы. Свойство 'locations' области создается асинхронно, и поэтому директива должна извлекать их асинхронно. Я буду обновлять ответ с помощью необходимого кода. –

11

Как вы говорите, вам не нужно запрашивать файл дважды. Передайте его с вашего контроллера на вашу директиву. Предполагая, что вы используете директиву в области видимости контроллера:

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

Затем в HTML (где вы призываете директивы).
Примечание:locations является ссылкой на ваши контроллеры $scope.locations.

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

И, наконец, в своей директиве

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

Это просто набросок, чтобы направить вас в правильном направлении, так что неполна и не тестируются.

+0

с использованием контроллера для повторного использования и получения данных с сервера? Вы серьезно думаете, что это работа для контроллера? –

+0

Я использую свой контроллер отдельно от своей директивы. Могу ли я выполнить то, что хочу, по вашей рекомендации? – Omegalen

+0

Я также использую контроллер отдельно от моей директивы (да, есть конкретные случаи, когда вы это сделаете), но это не имеет никакого смысла, если вы хотите использовать эти же данные в нескольких других местах вашего приложения. –

5

Что вам нужно правильно сервис:

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

locations будет кэшировать в службе, которая работала в качестве модели одноплодной. Это правильный способ получения данных.

Используйте этот сервис DataLayer в контроллере и директивы в порядке следующим образом:

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 
       }); 
      }); 
     } 
    }; 
}); 
Смежные вопросы