web-dev-qa-db-fra.com

AngularJS récupérer des données via AJAX avant que la directive ne soit appliquée

J'utilise les directivesuiMapd'AngularUI pour instancier une carte Google. La directive uiMap fonctionne parfaitement avec les données codées en dur ({mapOptions} et [myMarkers]). Cependant, je rencontre des problèmes lorsque je récupère ces données via $http.get()la directive est déclenchée avant que l'appel AJAX ne soit terminé).

Au départ, j'exécutais GET dans mon contrôleur GoogleMaps, mais lorsque j'ai réalisé que les choses ne se passaient pas dans l'ordre, j'ai déplacé GET dans la directive uiMap. J'ai 2 problèmes avec ça:

  1. Je pense que ce n'est pas la bonne façon de faire cela.
  2. L’EEG récupère également les données pour [myMarkers]
    • La fonction/directive qui crée les marqueurs est omniprésente car elle est responsable de la création de tous les superpositions

Ma question est donc la suivante: y a-t-il quelque part ailleurs dans l'application où je peux récupérer les données (et les appliquer à la portée) avant que la directive ne soit exécutée?

Je lis sur _ ($ q , et ça sonne comme ce que je veux, mais je ne sais pas si je peux le faire dans mon contrôleur plutôt que dans la directive (et je ne sais pas non plus en quoi $q.defer.resolve() est différent de $http.success()).

EDITLe code que j'utilise est en grande partie copié/collé à partir de la documentation d'AngularUI, mais voici un plunk: http://plnkr.co/edit/t2Nq57

Solution

Basé sur Andy answer , j’ai utilisé une combinaison de uiMap etuiIf:

<!-- 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>

Caveat 1 uiIf ne peut pas être dans le même élément qui spécifie le contrôleur fournissant sa condition (uiIf a une priorité supérieure à celle de ngController, afin que son contrôleur ne soit pas défini avant l'exécution de uiIf).

Caveat 2 Assurez-vous d'utiliser le plus version récente de uiIf (la version fournie dans la balise la plus récente, v0.3.2, est obsolète). L'ancienne a un bogue qui cause une erreur TypeError. dans certaines circonstances.

Caveat 3 jQuery DOIT être inclus avant AngularJS (dans index.html); sinon, vous recevrez une erreur TypeError indiquant que Object [object Object] has no method 'trigger'ou Object [object HTMLDivElement] has no method 'trigger' sous Windows). Chrome vous permettra d'entrer dans la fonction de déclenchement, car Chrome le sait, mais Angular ne le sait pas (et Angular renvoie l'erreur).

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{}

Drawback uiMap ne prend actuellement pas en charge les générateurs de rendu sur domready. Je cherche une version alternative de uiMapMarker suggérée dans ce _ (GitHub issue/comment .
Solution à ce problème: https://stackoverflow.com/a/14617167/758177
Exemple de travail: http://plnkr.co/edit/0CMdW3?p=preview

16
jacob

Vous pouvez simplement retarder l'exécution de ui-map jusqu'à ce que vos données soient chargées.

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;
});
31
Andrew Joslin

En règle générale, vous pouvez configurer votre directive, commencer le chargement et terminer le processus avec succès. Je suppose que vous voulez charger une seule donnée pour toutes les instances de votre directive. Alors, voici un code pseudo qui explique comment vous pourriez vouloir attaquer ceci:

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

Quoi qu'il en soit, j'espère que cela aide.

6
Ben Lesh

Je ne sais pas si cela aidera sans voir le code, mais j'ai rencontré le même problème lorsque je créais mon objet $scope.markers dans la fonction $http.success. J'ai fini par créer le $scope.markers = [] avant la fonction $http et, à l'intérieur de la fonction .success, j'ai rempli le tableau $scope.markers avec les données de retour.

Ainsi, l'objet $ scope a été lié pendant la compilation de la directive et mis à jour lorsque les données ont été renvoyées.

[SUGGESTION DE MISE À JOUR]

Avez-vous essayé de profiter resolve de votre itinéraire?

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

Je mets généralement mes demandes $ http dans un service, mais vous pouvez appeler le $ http directement depuis votre route:

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

Ensuite, dans votre contrôleur, injectez data et définissez vos éléments $scope sur les données.

1
Ben Felda