Последнее время мне довольно часто приходится работать с различными фреймворками, предназначенными для разработки JavaScript приложений. В основном с Backbone.js и AngularJS. И впечатления в целом очень приятные. Они действительно позволяют ускорить разработку и упростить поддержку кода.
Естественно, всё имеет свою цену. В данном случае это «порог вхождения» и время на изучение особенностей фреймворков. Кроме того, сейчас ведётся много споров на тему того какой фреймворк лучше. Участвовать в них у меня желания нет, хотя читать такие обсуждения иногда бывает интересно 🙂 К сожалению (или к счастью), победителей в этих спорах нет и, скорее всего, не будет. В большинстве случаев, чем больше работы за вас выполняет фреймворк, тем медленнее он работает. Но, с другой стороны, при этом уменьшается время разработки.
Поэтому, на мой взгляд, имеет смысл поработать с несколькими фреймворками разного уровня и сформировать представление в каких случаях имеет смысл их использовать.
В этой статье мы рассмотрим пример использования AngularJS.
Как раз несколько дней назад я опубликовал статью с очень похожим названием – Google maps & jQuery: позиционирование карты, в которой был показан небольшой пример использования Google maps API и библиотеки jQuery.
Мы перепишем этот пример и посмотрим, как изменятся структура приложения и количество кода.
Примечание. Скачать код можно на GitHub.
SourceА на работающее приложение посмотреть на демонстрационной страничке.
DemoНо прежде чем сравнивать код, разберём несколько моментов.
- jQuery не является аналогом AngularJS (и наоборот). И в этой статье мы их не сравниваем. Сокращённая версия jQuery входит в AngularJS и используется для «низкоуровневой» работы.
- Когда вы работаете только с jQuery, вы определяете структуру приложения сами. Библиотека упрощает работу с DOM и поддержку различных браузеров. Но, например, jQuery не будет проверять состояние объектов в вашем приложении и при этом каким-то образом изменять разметку.
- 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="https://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="https://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 мы не получили. Тем не менее, несколько преимуществ есть:
- Вся разметка убрана из JS кода.
- HTML-разметка легче читается. Безусловно, нужно разобраться с атрибутами AngularJS (и не только с ними), но глядя на разметку, вы сможете сказать, где и какой код будет выполнен и какие данные вставлены.
- Фреймворк сам следит за отключением обработчиков событий, которые не используются, и тому подобными вещами. Для данного примера это не очевидно, но в больших приложениях поиск таких багов не очень приятная задача 🙂
Надеюсь, статья вам понравилась. Если есть вопросы, замечания или пожелания, пишите!


