Google maps & AngularJS: позиционирование карты

Владимир | | Ajax, AngularJS, HTML, JavaScript, Web разработка.

google maps angularjs

Последнее время мне довольно часто приходится работать с различными фреймворками, предназначенными для разработки JavaScript приложений. В основном с Backbone.js и AngularJS. И впечатления в целом очень приятные. Они действительно позволяют ускорить разработку и упростить поддержку кода.

Естественно, всё имеет свою цену. В данном случае это «порог вхождения» и время на изучение особенностей фреймворков. Кроме того, сейчас ведётся много споров на тему того какой фреймворк лучше. Участвовать в них у меня желания нет, хотя читать такие обсуждения иногда бывает интересно 🙂 К сожалению (или к счастью), победителей в этих спорах нет и, скорее всего, не будет. В большинстве случаев, чем больше работы за вас выполняет фреймворк, тем медленнее он работает. Но, с другой стороны, при этом уменьшается время разработки.

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

В этой статье мы рассмотрим пример использования AngularJS.

Как раз несколько дней назад я опубликовал статью с очень похожим названием – Google maps & jQuery: позиционирование карты, в которой был показан небольшой пример использования Google maps API и библиотеки jQuery.

Мы перепишем этот пример и посмотрим, как изменятся структура приложения и количество кода.

Примечание. Скачать код можно на GitHub.

Source

А на работающее приложение посмотреть на демонстрационной страничке.

Demo

Но прежде чем сравнивать код, разберём несколько моментов.

  1. jQuery не является аналогом AngularJS (и наоборот). И в этой статье мы их не сравниваем. Сокращённая версия jQuery входит в AngularJS и используется для «низкоуровневой» работы.
  2. Когда вы работаете только с jQuery, вы определяете структуру приложения сами. Библиотека упрощает работу с DOM и поддержку различных браузеров. Но, например, jQuery не будет проверять состояние объектов в вашем приложении и при этом каким-то образом изменять разметку.
  3. AngularJS наоборот, связывает ваш JS-код и разметку страницы, т.е. сам отслеживает возникновение событий и изменение состояний объектов. Естественно это уменьшает количество кода, который вы должны сами написать, но при этом структура страницы и ваш код должны быть «понятными» для AngularJS.

Переходим к нашему приложению.

Прежде всего, рассмотрим разметку страницы

Вариант при использовании jQuery выглядит так:

<!DOCTYPE HTML>
<html lang="en-US">
<head>
	<meta charset="UTF-8">
	<title>Google Maps positioning</title>
	<link rel="stylesheet" href="styles.css">
</head>
<body>
	<header>Google maps positioning</header>
	<div class="content">
		<div id="cities"></div>
		<div id="map"></div>
	</div>
	<footer><a href="http://www.simplecoding.org/">simplecoding.org</a></footer>
	<script src="//maps.googleapis.com/maps/api/js?sensor=false"></script>
	<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
	<script src="main.js"></script>
</body>
</html>

Для AngularJS нужно сделать несколько изменений.

<!DOCTYPE HTML>
<html lang="en-US" ng-app="mapPositioningApp">
<head>
	<meta charset="UTF-8">
	<title>Google Maps positioning</title>
	<link rel="stylesheet" href="styles.css">
</head>
<body ng-controller="MapPositioning">
	<header>Google maps positioning</header>
	<div class="content">
		<div id="cities">
			<ul>
				<li ng-repeat="city in cities" ng-click="showCity(city)">{{ city.city }}</li>
			</ul>
		</div>
		<div id="map" ng-init="initialize()"></div>
	</div>
	<footer><a href="http://www.simplecoding.org/">simplecoding.org</a></footer>
	<script src="//maps.googleapis.com/maps/api/js?sensor=false"></script>
	<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.6/angular.min.js"></script>
	<script src="main.js"></script>
</body>
</html>

Как видите, размер страницы немного увеличился, и появились специальные атрибуты (с префиксом ng-). Именно с их помощью происходит «связывание» JS кода и элементов страницы. Рассмотрим их по порядку.

ng-app="mapPositioningApp" (строка 2) – с помощь этого атрибута мы указали, что вся наша страница (мы установили этот атрибут для тега html) относится к приложению mapPositioningApp. AngularJS позволяет использовать несколько приложений на одной странице, и область их работы будет ограничиваться тегами, для которых установлен ng-app.

Теперь нужно создать само приложение. Для этого в файл main.js добавляем строку:

var mapPositioningApp = angular.module('mapPositioningApp',[]);

angular – это объект, который создаёт фреймворк, а мы просто добавили модуль с названием mapPositioningApp.

Затем тегу body мы добавили атрибут ng-controller="MapPositioning", т.е. указали, что управлять содержимым этого тега будет контроллер под названием MapPositioning. В приложение может входить несколько контроллеров, но в данном случае нам достаточно одного.

Теперь рассмотрим код контроллера (main.js)

mapPositioningApp.controller('MapPositioning', ['$scope', '$http', function($scope, $http) {
    $scope.cities = [];
    $scope.map;
    $scope.infoBox = new google.maps.InfoWindow();
    var mapContainer = document.getElementById('map');
    mapContainer.style.width = '70%';
    mapContainer.style.height = '500px';

    $http.get('data.json').success(function(data) {
        $scope.cities = data;
    });

    $scope.initialize = function() {
        var mapOptions = {
            center: new google.maps.LatLng(50.5, 30.5),
            zoom: 8,
            mapTypeId: google.maps.MapTypeId.ROADMAP
        };
        $scope.map = new google.maps.Map(mapContainer, mapOptions);
    }

    $scope.showCity = function(city) {
        var coords = new google.maps.LatLng(city.lat, city.lng);
        $scope.infoBox.setContent(city.city + ' - ' + city.desc);
        $scope.infoBox.setPosition(coords);
        $scope.infoBox.open($scope.map);
        $scope.map.setCenter(coords);
    }
}]);

Прежде всего, обратите внимание на объявление контроллера. В принципе, можно просто объявить функцию

function MapPositioning($scope, $http) {
	...
}

Такой вариант будет работать, но функция будет объявлена в глобальном пространстве имён, а это не очень хорошая практика, т.к. могут возникнуть коллизии имен.

Кроме того, обратите внимание на объекты $scope и $http, которые контроллер получает в качестве параметров. Эти объекты создаёт фреймворк, причём он анализирует имена параметров для того, чтобы определить какие именно объекты собирается использовать контроллер. Поэтому если вы сожмете код с помощью packer.js, то эти название изменятся, и приложение работать перестанет. Чтобы этого избежать, методу controller во втором параметре передают массив, в который входят названия всех параметров в виде текстовых строк, а затем сама функция контроллера.

Теперь просмотрите внимательно код контроллера. Видите, в нём отсутствует код отображения списка городов на странице. В версии с использованием jQuery мы создали список с названиями городов и обработчиками кликов по ним.

$.getJSON('data.json', function(data) {
	if (data.length > 0) {
		var list = $('<ul>');
		$.each(data, function(index, city) {
			var item = $('<li>')
				.on('click', city, showCity)
				.html(city.city);
			list.append(item);
		});
		$('#cities').html(list);
	}
});

Конечно, можно было использовать какой-нибудь шаблонизатор. Это позволило бы немного сократить код, и читался бы он легче. Но сути это не меняет. Мы самостоятельно формируем разметку, устанавливаем обработчики и отображаем код на странице.

Теперь посмотрите на аналогичный код при использовании AngularJS. Он состоит из двух частей.

JS кода (main.js):

$scope.cities = [];
$http.get('data.json').success(function(data) {
    $scope.cities = data;
});

И разметки (index.html)

<ul>
	<li ng-repeat="city in cities" ng-click="showCity(city)">{{ city.city }}</li>
</ul>

Разметка используется как шаблон для вставки данных из массива $scope.cities. При этом, AngularJS сам следит за значениями, записанными в $scope.cities и при их изменении заново формирует разметку. Т.е. как только мы получаем ответ на AJAX запрос и обновляем значения массива ($scope.cities = data) сразу же происходит обновление списка. Это поведение называется Two-way data binding – двунаправленное связывание данных (если кто-то знает более удачный вариант перевода, напишите).

Естественно, AngularJS не может сам угадать, как именно мы хотим вывести данные. Для этих целей используются специальные атрибуты.

ng-repeat="city in cities" – дублирует тег значения для всех значений массива. В данном случае для каждого элемента массива cities создаётся тег li.

ng-click="showCity(city)" – устанавливает обработчик события onclick. В качестве обработчика мы используем функцию showCity и при этом передаём ей в качестве параметра данные текущего города. Сама функция showCity полностью совпадает с версией для jQuery и мы её подробно рассматривали в прошлой статье.

{{ выражение }} – позволяет вывести значение переменной или выполнить JS код. Здесь мы выводим название города.

Кстати, обратите внимание на использования объекта $scope. Этот объект служит промежуточным звеном между JS кодом и шаблоном (HTML разметкой). Внутри HTML разметки нам доступны только атрибуты этого объекта.

Последний шаг – инициализация карты

Здесь мы использовали атрибут ng-init="initialize()". Т.е. вызвали функцию initialize() сразу после инициализации самого приложения. Код инициализации карты также не отличается от версии для jQuery. В данном случае задача простая и мы всю работу с картами сделали вручную, т.е. напрямую работали с API карт, но существуют дополнительные директивы для AngularJS, которые упрощают работу с картами.

Выводы

Данный пример довольно простой и если сравнивать по количеству кода, то особого выигрыша за счёт использования AngularJS мы не получили. Тем не менее, несколько преимуществ есть:

  1. Вся разметка убрана из JS кода.
  2. HTML-разметка легче читается. Безусловно, нужно разобраться с атрибутами AngularJS (и не только с ними), но глядя на разметку, вы сможете сказать, где и какой код будет выполнен и какие данные вставлены.
  3. Фреймворк сам следит за отключением обработчиков событий, которые не используются, и тому подобными вещами. Для данного примера это не очевидно, но в больших приложениях поиск таких багов не очень приятная задача 🙂

Надеюсь, статья вам понравилась. Если есть вопросы, замечания или пожелания, пишите!

  • Pingback: отдых()

  • Pingback: заказ авиабилетов онлайн()

  • Pingback: бронирование авиабилетов()

  • Pingback: Russia()

  • Pingback: new construction 92363()

  • MOTORIST

    +++++
    Хотелось бы больше статей по Angular (архитектура, работа в связках и т.д.).
    node.js + Angular + moongoDB (может быть есть более интересные связки)
    роутинг

    • Статьи по Angular однозначно будут. Только один вопрос. Почему именно node.js? Angular по большому счёту без разницы какая именно технология используется на сервере.

  • operun

    Не получается вставить яндекс карту. В index.html добавлены:

    А в main.js изменена функция initialize():

    $scope.initialize = function() {
    var mapOptions = {
    center: [50.5, 30.5],
    zoom: 8
    type: 'yandex#publicMap',
    behaviors: ['default', 'scrollZoom']
    };
    $scope.map = new ymaps.Map(mapContainer, mapOptions);
    }

    • У вас запятая после zoom: 8 пропущена. Firebug должен был показать ошибки в консоли.

      • operun

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

        • operun

          Firebug ругается на «ymaps.Map is not a constructor» — это означает что api яндекса еще не загрузилось, а мы уже пытаемся создать объект (карту). В классическом варианте делается через:

          ymaps.ready(init); //после загрузки апи инициализируем карту (запускаем функцию init()).
          function init(){ }

          А как тогда это реализовать в Angular ?

        • Используйте атрибут ng-init.
          ng-init=»initialize()»
          Пример смотрите в этой статье «Последний шаг – инициализация карты».

        • operun

          ng-init=»initialize()» изначально и используется, все как в статье. Изменению подверглись только те строки которые я описал в первом своем сообщении.

          Второй вариант рабочий, получилось сделать через корявый костыль, а именно:
          заменен на:

          А в main.js добавил одну строку и старт функции происходит теперь так:
          $timeout($scope.initialize, 3000); //за 3 секунды api яндекса успевает загрузится, если поставить меньше то карта не будет отображаться.

        • Странно, я перепроверил свой пример. Карты Google не меньше чем яндекса, но мой код выполняется только после полной загрузки карты.

          Проверьте в каком порядке вы загружаете скрипты.

        • operun

          Кстати, если спамить F5 то 1 раз из 20 карта загружается)

        • Потому что код карт берется из кэша браузера.

        • Дмитрий Афанасьев

          В Api Яндекс Карт об этом написано.
          Чтобы карта полностью загрузилась надо использовать функцию ymaps.ready

          вот так работает
          $scope.initialize = function () {
          var mapOptions = {
          center: [50.5, 30.5],
          zoom: 8
          };
          ymaps.ready(function () {
          $scope.map = new ymaps.Map(mapContainer, mapOptions);
          })
          }

  • bobpps

    Большое спасибо!

  • Евгений

    А почему не отображаются маркеры,

    • В этом примере я использовал только InfoWindow