AngularJS + Leaflet — Инициализировать карту листовок в сервисе.

Я создаю приложение на AngularJS, которое использует LeafletJS для взаимодействия с картой, предлагая различные возможные взаимодействия, разделенные на то, что я называю фазами. Для каждой из этих фаз существует состояние UIRouter со своим контроллером и шаблоном.

В настоящее время я предоставляю функциональность листовки через службу. Идея заключалась в том, чтобы эта служба инициализировала карту Leaflet и предоставила некоторый ограниченный доступ к контроллеру состояния. Таким образом, эти контроллеры будут вызывать сервисные функции, такие как setupMarkersInteractions, для настройки обратных вызовов, которые, например, позволяют размещать маркеры на карте.

Однако у меня возникла проблема при инициализации карты через функцию leaflet.map() Leaflet, а именно: Error: Map container not found. Это связано с тем, что Leaflet не может найти элемент HTML, с которым должна быть связана карта.

В настоящее время я вроде делаю это:

function mapService() { 
  var map;

  return {
    initializeMap : initializeMap,
    setupMarkersInteractions : setupMarkersInteractions
  };

  function initializeMap() {
    map = leaflet.map('map');
  }

  function setupMarkersInteractions() {
    map.on('click', markerPlacementCallback);
  }
}

Функция initializeMap сообщает leaflet.map искать элемент HTML с id='map', который объявлен в шаблоне состояния.

Теперь, сам вопрос, связано ли это с какой-то неспособностью служб AngularJS получить доступ к HTML-шаблону? Я ничего не смог найти по этому вопросу, но я подумал, что для сервисов имеет смысл не обращаться к представлению напрямую...
Если да, то какой обходной путь мне следует изучить? Я изучил leaflet-directive, но, похоже, он не дает возможности добавлять и удалять пользовательские обратные вызовы с той гибкостью, которую я хотел бы (например, когда я добавляю функцию свободного рисования с помощью Leaflet-Freedraw, все усложняется).

Я рассматривал возможность использования leaflet.map напрямую с аргументом HTMLElement для элемента, но все же не смог заставить его работать, хотя есть вероятность, что я не передаю то, что должно.


person pavlag    schedule 30.11.2015    source источник


Ответы (1)


Происходит то, что в тот момент, когда L.Map пытается получить доступ к DOM из вашего сервиса, шаблон еще доступен. Обычно сервисы загружаются и внедряются в контроллеры, контроллеры инициализируют свои области, после чего шаблоны инициализируются и добавляются в DOM. Вы увидите, если вы поместите большой тайм-аут на инициализацию вашей карты, что он найдет свой элемент DOM. Но это очень уродливый хак. В Angular вы должны использовать директиву для добавления логики к элементам DOM.

Например, шаблон: <leaflet></leaflet> и его очень простая директива:

angular.module('app').directive('leaflet', [
  function () {
    return {
      replace: true,
      template: '<div></div>',
      link: function (scope, element, attributes) {
        L.map(element[0]);
      }
    };
  }
]);

Вы можете подключить это к своей службе и передать элемент в свой метод инициализации:

angular.module('app').directive('leaflet', [
           'mapService'
  function (mapService) {
    return {
      replace: true,
      template: '<div></div>',
      link: function (scope, element, attributes) {
        mapService.initializeMap(element[0]);
      }
    };
  }
]);

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

angular.module('app').factory('leaflet', [
             '$q',
    function ($q) {
        var deferred = $q.defer();
        return {
          map: deferred.promise,
          resolve: function (element) {
            deferred.resolve(new L.Map(element));
          }
        }
    }
]);

angular.module('app').directive('leaflet', [
           'leaflet',
  function (leaflet) {
    return {
      replace: true,
      template: '<div></div>',
      link: function (scope, element, attributes) {
        leaflet.resolve(element[0]);
      }
    };
  }
]);

Если вы хотите использовать экземпляр карты в своем контроллере, теперь вы можете подождать, пока он не будет разрешен:

angular.module('app').controller('rootController', [
           '$scope', 'leaflet',
  function ($scope,   leaflet) {
    leaflet.map.then(function (map) {
      var tileLayer = L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
        attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>',
        maxZoom: 18
      }).addTo(map);
      map.setView([0, 0], 1);
      L.marker([0, 0]).addTo(map);
    });
  }
]);

Вот пример концепции Plunker: http://plnkr.co/edit/DoJpGqtR7TWmKAeBZiYJ?p=preview

person iH8    schedule 30.11.2015
comment
Спасибо! Я забыл упомянуть, что уже пытался использовать пользовательскую директиву, подобную той, которую вы написали здесь; однако в то время я не мог проверить это в своих модульных тестах и ​​в конце концов забыл об этой опции. Однако часть обещаний - это то, о чем я никогда не думал... Попытаюсь реализовать это прямо сейчас! - person pavlag; 30.11.2015
comment
Здесь также есть очень простая директива для листовок: github.com/CleverMaps/tiny-leaflet-directive - person mattesCZ; 30.11.2015