AngularJS извлекает данные через AJAX перед запуском директивы

Я использую директивы AngularUI uiMap для создания экземпляра Google Map. Директива uiMap прекрасно работает с жестко заданными данными ({mapOptions} и [myMarkers]); однако у меня возникают проблемы, когда я получаю эти данные через $http.get() (директива срабатывает до завершения вызова AJAX).

Первоначально я выполнял GET в своем контроллере GoogleMaps, но когда я понял, что все происходит не по порядку, я переместил GET в директиву uiMap. У меня есть 2 проблемы с этим:

  1. Я думаю, что это не правильный способ сделать это.
  2. The GET also retrieves the data for [myMarkers]
    • The function/directive that creates the markers is ubiquitous in that it is responsible for creating all overlays

Итак, мой вопрос: есть ли где-то еще в приложении, где я могу получить данные (и применить их к области) до запуска директивы?

Я прочитал $q, и похоже, что это то, что я хочу, но я не уверен, смогу ли я сделать это в своем контроллере, а не в директиве (также не уверен, чем $q.defer.resolve() отличается от $http.success()).

EDIT Большая часть кода, который я использую, это копирование/вставка из документа AngularUI, но вот рывок: http://plnkr.co/edit/t2Nq57

Решение

Основываясь на ответе Энди, я использовал комбинацию uiMap и uiIf:

<!-- index.html -->
<div
  id="map_container"
  ng-controller="GoogleMaps">

  <div ui-if="mapReady">

    <div
      ng-repeat="marker in markers"
      ui-map-marker="markers[$index]"
      ui-event="{'map-click':'openMarkerInfo(marker)'}"
    ></div>

    <div
      ui-map-info-window="myInfoWindow"
      ng-include="'infobox.html'"
    ></div>

    <div
      id="map_canvas"
      ui-map="myMap"
      ui-options="mapOptions"
    ></div>

  </div>

</div>

Предупреждение 1 uiIf не может находиться в том же элементе, который указывает контроллер, предоставляющий его условие (uiIf имеет более высокий приоритет, чем ngController, поэтому его контроллер не будет установлен до выполнения uiIf).

Предупреждение 2 Обязательно используйте самые последняя версия uiIf (версия, представленная в самом последнем теге v0.3.2, устарела). У старого есть ошибка, вызывающая TypeError при определенных обстоятельствах.

Предупреждение 3 jQuery ДОЛЖЕН быть включен перед AngularJS (в index.html); иначе вы получите TypeError о том, что Object [object Object] has no method 'trigger' (или Object [object HTMLDivElement] has no method 'trigger' в Windows). Chrome позволит вам войти в функцию триггера, потому что Chrome знает об этом, а Angular — нет (и Angular выдает ошибку).

function GoogleMaps( $scope , $http )
{

  var mapDefaults = {
    center:    new google.maps.LatLng(25,-90),//centres on Gulf of Mexico
    zoom:      4,
    mapTypeId: google.maps.MapTypeId.ROADMAP
  };

  $scope.mapOptions = {};
  $scope.mapReady = false;
  $scope.markers = [];

  $http.get('map.json').then(function mapData(response) {

    var map_data = response.data,
      user_defaults = map_data.user.defaults; //{center: [lat,lng], zoom: 15}

    $scope.mapOptions = {
      "center":    (typeof user_defaults.center !== 'undefined') ?
        new google.maps.LatLng(user_defaults.center[0],user_defaults.center[1])
        : mapDefaults.center,
      "zoom":      (typeof user_defaults.zoom !== 'undefined') ?
        parseInt(user_defaults.zoom,10)
        : mapDefaults.zoom,
      "mapTypeId": mapDefaults.mapTypeId
    };

    //working on code to populate markers object

    $scope.mapReady = true;

  });

  // straight from sample on http://angular-ui.github.com/#directives-map
  $scope.addMarker = function($event) { … };
  $scope.openMarkerInfo = function(marker) { … };
  $scope.setMarkerPosition = function(marker, lat, lng) { … };

}//GoogleMaps{}

Недостаток В настоящее время uiMap не поддерживает создателей рендеринга на domready. Я рассматриваю альтернативную версию uiMapMarker, предложенную в этом GitHub. проблема/комментарий.
Решение этой проблемы: https://stackoverflow.com/a/14617167/758177
Пример работы: http://plnkr.co/edit/0CMdW3?p=preview


person Jakob Jingleheimer    schedule 25.01.2013    source источник
comment
Можете ли вы опубликовать контроллер и сервисный код. У меня есть идея, но я хочу убедиться, что вы сейчас делаете.   -  person Ben Felda    schedule 25.01.2013
comment
@BenFelda добавила ответ на мой вопрос   -  person Jakob Jingleheimer    schedule 25.01.2013
comment
Являются ли жестко закодированные данные такими же, как вы возвращаете в map_data.locations? Извините, если это глупый вопрос, но, похоже, это должно сработать.   -  person Ben Felda    schedule 25.01.2013
comment
@BenFelda: нет, данные будут извлечены из базы данных (я просто использую файл json для имитации ответа).   -  person Jakob Jingleheimer    schedule 26.01.2013
comment
Пробовали ли вы статически вставлять данные, возвращаемые этой службой, в массив только для проверки данных?   -  person Ben Felda    schedule 26.01.2013
comment
@BenFelda: да, как в обратном вызове $http (игнорирование возвращаемых данных и жесткое кодирование объекта mapOptions), так и перед обратным вызовом. Жесткое кодирование перед GET позволяет директиве нормально функционировать; жесткое кодирование его в обратном вызове GET приводит к TypeError (mapOptions пуст, когда директива пытается его использовать). Но сами данные действительны и имеют правильный формат для директивы (и API Карт Google).   -  person Jakob Jingleheimer    schedule 26.01.2013


Ответы (3)


Вы можете просто отложить выполнение ui-map до тех пор, пока ваши данные не будут загружены.

HTML:

<div ui-if="loadingIsDone">
  <div ui-map="myMap" ui-options="myOpts"></div>
</div>

JS:

$http.get('/mapdata').then(function(response) {
  $scope.myOpts = response.data;
  $scope.loadingIsDone = true;
});
person Andrew Joslin    schedule 25.01.2013
comment
Я пробовал это, но тогда карта никогда не появляется. Я проверил $scope.mapReady после .then(), и его значение равно true. Я обновил plunk, но версия в plunk жалуется на TypeError, которую я не получаю локально (и я копирую/вставляю целые файлы из своего локального, только изменяя имя файла app.js angular-ui на angular_ui.js чтобы избежать конфликта имен). - person Jakob Jingleheimer; 26.01.2013
comment
нашел источник TypeError. По какой-то причине значения, которые я присваиваю $scope.mapOptions внутри .then(), не сохраняются (mapOptions пусто после/снаружи .then()). - person Jakob Jingleheimer; 26.01.2013
comment
Я думаю, что ui-if не работает (или работает не так, как ожидалось): я обернул свой ui-map div в ui-if, и он все еще отображается в DOM, но не (никогда) не появляется, и angular все еще пытается обработать uiMap (именно это, казалось бы, ui-if должно остановить). - person Jakob Jingleheimer; 28.01.2013
comment
ui-if теперь является частью ядра Angular как ngIf. - person Engineer; 24.10.2013

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

app.directive('myDelayedDirective', ['$http', '$q', function($http, $q) {

  //store the data so you don't load it twice.
  var directiveData,
      //declare a variable for you promise.
      dataPromise;

  //set up a promise that will be used to load the data
  function loadData(){ 

     //if we already have a promise, just return that 
     //so it doesn't run twice.
     if(dataPromise) {
       return dataPromise;
     }

     var deferred = $q.defer();
     dataPromise = deferred.promise;

     if(directiveData) {
        //if we already have data, return that.
        deferred.resolve(directiveData);
     }else{    
        $http.get('/Load/Some/Data'))
          .success(function(data) {
              directiveData = data;
              deferred.resolve(directiveData);
          })
          .error(function() {
              deferred.reject('Failed to load data');
          });
     }
     return dataPromise;
  }

  return {
     restrict: 'E',
     template: '<div>' + 
          '<span ng-hide="data">Loading...</span>' +
          '<div ng-show="data">{{data}}</div>' + 
        '</div>',
     link: function(scope, elem, attr) {
         //load the data, or check if it's loaded and apply it.
         loadData().then(function(data) {
             //success! set your scope values and 
             // do whatever dom/plugin stuff you need to do here.
             // an $apply() may be necessary in some cases.
             scope.data = data;
         }, function() {
             //failure! update something to show failure.
             // again, $apply() may be necessary.
             scope.data = 'ERROR: failed to load data.';
         })
     }
  }
}]);

Во всяком случае, я надеюсь, что это поможет.

person Ben Lesh    schedule 25.01.2013
comment
Я бы предпочел не выполнять GET в директиве (я не думаю, что это относится к ней?), но это также является проблемой, потому что область действия изолирована от этой директивы, а директиве маркеров нужны некоторые данные, полученные в начальный ПОЛУЧИТЬ. - person Jakob Jingleheimer; 25.01.2013
comment
Вы можете легко разбить этот GET на службу, и это будет то же самое. Для простоты я не делал этого в своем примере... если у вас есть другая директива, требующая тех же данных, тогда вы должны взять всю логику с GET и его обещанием и поместить это в сервис, а затем иметь оба директивы вызывают loadData для этой службы. - person Ben Lesh; 26.01.2013
comment
Мне это больше нравится. Но я все еще сталкиваюсь с проблемой, что директива, отвечающая за создание маркеров, также является директивой, отвечающей за создание информационных окон и наложений областей. Таким образом, добавление вызова службы в эту директиву неприменимо в 2/3 того, что он делает. - person Jakob Jingleheimer; 26.01.2013

Я не уверен, поможет ли это, не видя кода, но я столкнулся с той же проблемой, когда создавал свой объект $scope.markers внутри функции $http.success. В итоге я создал $scope.markers = [] перед функцией $http, а внутри функции .success я заполнил массив $scope.markers возвращаемыми данными.

Таким образом, объект $scope был привязан во время компиляции директивы и обновлен при возврате данных.

[ОБНОВЛЕНИЕ ПРЕДЛОЖЕНИЯ]

Вы пробовали использовать resolve на своем маршруте?

  function($routeProvider) {
    $routeProvider.
      when(
        '/',{
          templateUrl: 'main.html',
          controller: Main,
          resolve: {
               data: function(httpService){
                   return httpService.get()
               }
          }
      }).
      otherwise({redirectTo: '/'});
  }

Обычно я помещаю свои запросы $http в службу, но вы можете вызвать $http прямо из вашего маршрута:

App.factory('httpService'), function($http){
     return {
       get: function(){
           $http.get(url)
       }
    }
});

Затем в своем контроллере введите data и установите элементы $scope в данные.

person Ben Felda    schedule 25.01.2013
comment
Звучит странно, что обновление ссылки работает, а объявление новой — нет. Можете ли вы открыть сообщение об ошибке со ссылкой на этот поток/пример на AngularUI? - person ProLoser; 25.01.2013
comment
@ProLoser Мой ответ больше связан с Angular в целом и привязкой к контроллеру при использовании $http. Я думаю, что проблема не имеет ничего общего с AngularUI. - person Ben Felda; 25.01.2013
comment
Теперь, когда я вижу планк, мой текущий ответ не имеет значения. - person Ben Felda; 25.01.2013
comment
@BenFelda Я добавил 2 экземпляра свойства $scope перед GET по вашему предложению (его не было с самого начала) - person Jakob Jingleheimer; 25.01.2013
comment
Не будет ли это вводить данные в контроллер Main (вместо контроллера GoogleMaps)? - person Jakob Jingleheimer; 29.01.2013
comment
@jacob Извините, я пропустил это. Вы правы, контроллер Main будет получать модель, однако мне любопытно, где Main вообще используется? Ну что ж. Мне нравится ответ Энди, не думал об этом. Я надеюсь, что у вас получилось. - person Ben Felda; 30.01.2013
comment
@BenFelda, я сделал, спасибо! Я опубликую решение позже сегодня. Попытка заставить маркеры отображаться при загрузке (что AngularUI uiMap изначально не поддерживает, поэтому переписывает директиву uiMarkers/пытается использовать github.com/angular-ui/angular-ui/issues/). Спасибо! Полезно знать о resolve: :) - person Jakob Jingleheimer; 30.01.2013
comment
@BenFelda, кстати, Main используется для отправки и получения сообщений / push-уведомлений WebSocket (и нескольких других очень мелких вещей, которые больше нигде не используются). Когда я удаляю приложение, Main может стать ошибочным (маленькие/нестандартные вещи могут стать достаточно большими, чтобы гарантировать наличие собственного контроллера). - person Jakob Jingleheimer; 30.01.2013