Последнее время мне довольно часто приходится работать с различными фреймворками, предназначенными для разработки 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 (и не только с ними), но глядя на разметку, вы сможете сказать, где и какой код будет выполнен и какие данные вставлены.
- Фреймворк сам следит за отключением обработчиков событий, которые не используются, и тому подобными вещами. Для данного примера это не очевидно, но в больших приложениях поиск таких багов не очень приятная задача 🙂
Надеюсь, статья вам понравилась. Если есть вопросы, замечания или пожелания, пишите!