SPA – это просто, или Знакомство с AngularJS Максим Ловин О себе • В IT c 2009 года • В DataArt c 2014 года • Senior .NET/Front end developer • Maxim.Lovin@dataart.com О чем будем говорить • Тренды разработки программного обеспечения • Что такое SPA • Из чего состоит AngularJS • Демо приложение • Модульность AngularJS приложений • Презентационная логика и контроллеры • Bindings, watchers, digest cycle • Бизнес логика и сервисы • Директивы и фильтры • Роутинг • Исходный код демо приложения • Рекомендуемые материалы Тренды разработки программного обеспечения • Web applications • Не зависят от платформы клиента • Упрощается поддержка приложения • Повышеная безопасность данных • Стремительное развитие технологий (HTML5, FileApi, WebGL) • Service-oriented architecture • Mobile • Single Page Application Что такое SPA Это Web-приложение, которое выполняется непосредственно на стороне клиента в Web-браузере, написанное на HTML, JavaScript и CSS. Преимущества SPA • Поведение native приложения на любой платформе • Навигация без перезагрузки страницы с сохранением истории • Улучшенное взаимодействие между визуальными компонентами • Меньше нагрузка на сервер, больше на клиент • Загрузка преимущественно статического контента и только 1 раз • Навигация/рендеринг на клиенте • В процессе работы обращение к серверу только за данными • Возможность работы offline Недостатки SPA • Сравнительно долгая инициализация приложения • Нет возможности скрыть логику • Нет ошибок компиляции, более сложная отладка • Нужен более детальный Code Review и более строгие конвенции кодирования • Желательно периодическое профилирование приложение с целью выявления утечек памяти и проблем с производительностью • Затруднительное SEO AngularJS AngularJS – это JavaScript framework, предназначенный для упрощения разработки SPA на основе шаблона MVC. Файл Модуль Для чего нужен angular.js ng Ядро angular + множество компонент для обеспечения базовой функциональности angularanimate.js ngAnimate Обеспечивает поддержку для JavaScript, CSS3 transition и CSS3 keyframe анимаций angularcookies.js ngCookies Компоненты для работы с cookies браузера angularresource.js ngResource Сервис для упрощенной работы с RESTful сервисами angularroute.js ngRoute Компоненты для реализации роутинга в приложении angulartouch.js ngTouch Компоненты для работы с сенсортыми экранами Demo • Скриншот Modules • Контейнер для различных частей приложения • Может содержать контроллеры, сервисы, фильтры, директивы • Конфигурирует инжектор • Создание модуля: angular.module(‘moduleName’, [ ‘dependencyModule1’, ‘dependencyModule2’, …]); • Обращение к модулю: angular.module(‘moduleName’); Modules • Конфигурация модуля: angular.module(‘moduleName’, […]) .config([‘srv1Provider’, ‘srv2Provider’, function(srv1Provider, srv2Provider){ //config code }]); • Запуск модуля: angular.module(‘moduleName’, […]) .run([‘srv1’, ‘srv2’, function(srv1, srv2){ //run code }]); • Установка модуля приложения: <html ng-app=“moduleName”> Modules Angular application rootModule .config() .run() ng-app module1 services directives controllers filters module2 services directives controllers filters module3 services directives controllers filters module4 services directives controllers filters module5 services directives controllers filters module6 services directives controllers filters Функциональная схема приложения Angular application View Directives Filters Scope Controller Services Controller • Контроллер содержит презентационную логику • Определяется: angular.module(‘myModule', []).controller(‘myController', ['$scope', ‘myService', function ($scope, myService) { $scope.inputText = myService.getInitialText(); $scope.btnTitle = ‘Do action’; $scope.btnAction = function() { myService.doAction($scope.inputText); }; }]); • Используется: <div ng-controller=“myController”> <input type=“text” ng-model=“inputText” /> <button ng-click=“btnAction()”>{{ btnTitle }}</button> </div> Dependency injection angular.module(‘myModule', []).controller(‘myController', function ($scope, myService) { //presentation logic here }); ------------------------------------------------------------angular.module(‘myModule', []).controller(‘myController', ['$scope', ‘myService', function ($scope, myService) { //presentation logic here }]); ------------------------------------------------------------var MyController = function($scope, myService){ //presentation logic here } MyController.$inject = ['$scope', ‘myService']; angular.module(‘myModule', []) .controller(‘myController', MyController); One way bindings $scope.someValue = ‘<span class=“styled-text”>Some value</span>’; $scope.anotherValue = function (){ return ‘1234567890123’; }; ---------------------------------------------------------<p>{{ someValue }}</p> <p ng-bind=“someValue” /> <!-- <span class=“styled-text”>Some value</span> --> <p ng-bind-html=“someValue” /> <!-- module ngSanitize required --> <!-- Some value --> <p>{{ anotherValue()|currency:‘€’ }}</p> <!-- €1,234,567,890,123.00 --> <p ng-bind=“anotherValue()|date:‘medium’” /> <!-- Feb 14, 2009 1:31:30 AM --> Two way bindings • Реализуется при помощи директивы ng-model • Применяется с любыми директивами, подразумевающими пользовательский ввод (‘input’, ‘select’, ‘textarea’) • Предоставляет возможность валидации • Хранит состояние сонтрола (valid/invalid, dirty/pristine, touched/untouched, validation errors) и назначает соответствующие классы (ng-valid, ng-invalid, ng-dirty, ngpristine, ng-touched, ng-untouched ) ---------------------------------------------------------$scope.someValue = ‘’; ---------------------------------------------------------<input type=“text” ng-model=“someValue” /> <p ng-bind=“someValue” /> Two way bindings options • Определяются с помощью директивы ng-model-options • Позволяют сконфигурировать: • По какому событию привязывать данные (updateOn) • Задержку привязки по каждому событию (debounce) • Допускать ли невалидные данные (allowInvalid) • Является ли привязываемое значение функцией (getterSetter) • Локаль даты и времени для input[type=“date”] и др. (timezone) ---------------------------------------------------------<input type=“text” ng-model=“someValue” ng-model-options=“{updateOn: ‘blur’, debounce: {‘blur’: 500}}” /> Watchers • Watching by reference • Watching collection contents • Watching by value ---------------------------------------------------------$scope.users = [{name:‘Mary’,points:100}, {name:‘Bob’,points:220}]; var newUsers = [{name:‘John’,points:330}, {name:‘Dee’,points:180}]; function listener(newValue, oldValue){} $scope.$watch(‘users’, listener); //by reference $scope.$watchCollection(‘users’, listener); //collection $scope.$watch(‘users’, listener, true); //by value $scope.users = newUsers; //by reference, collection, by value $scope.users.push({name:‘Alex’,points:320}); //collection, by value $scope.users[0].points = 150; //by value Digest cycle • Обновляет биндинги для изменившейся модели • Обрабатывает watchers • Начинает работу по любому событию, попавшему в контекст angular приложения, с помощью $scope.$digest() или $scope.$apply() ---------------------------------------------------------$scope.name = ‘Foo’; $scope.changeName = function() { $scope.name = ‘Bar’; }; ---------------------------------------------------------<p ng-bind=“name” /> <button ng-click=“changeName()”>Change name</button> ---------------------------------------------------------- Loop through watch list oldVal != newVal Run Yes Run watcher listener or mark binding as dirty oldVal = newVal Yes did we have at least one dirty? No Update DOM Services • Содержат бизнес логику приложения • Инжектируются в контроллеры, другие сервисы, директивы и фильтры • Singletons – имеют единственный экземпляр • Lazily instantiated – инстанциируются по первому запросу • Виды: • constant • value • factory • service • provider Constant • Содержит не изменяемое значение • Может быть в виде строки, числа, массива, объекта или функции • Может настраиваться в .config() модуля • Определяется: angular.module(‘myModule', []) .constant(‘myConstant', {value1: ‘someVal’, value2: 21}); • Настраивается: angular.module(‘myApp', [‘myModule']) .config([‘myConstant', function(c){ c.value1 = ‘anotherVal’; }]); Value • Содержит изменяемое значение • Может быть в виде строки, числа, массива, объекта или функции • Определяется: angular.module(‘myModule', []) .value(‘myValue', {value1: ‘someVal’, value2: 21}); Factory, Service angular.module(‘myModule', []) .factory(‘myFactory', [‘dependency1', ‘dep2', function (d1, d2){ var privateVariable = ‘’; function doSomethingPrivate(){ d1.doAction(privateVariable); } return { doSomething: function (p){ privateVariable = p; doSomethingPrivate(); } }; }]) .service(‘myService', [‘dependency1', ‘dep2', function (d1, d2){ var self = this; var privateVariable = ‘’; function doSomethingPrivate(){ d1.doAction(privateVariable); } self.doSomething = function (p){ privateVariable = p; doSomethingPrivate(); }; }]); Provider • Может настраиваться в .config() модуля • Определяется: angular.module(‘myModule', []).provider(‘myProvider', function (){ var privateVarConfig = ‘’; this.configurePrivate = function(val){ privateVarConfig = val; }; function MyServiceConstructor(d1, d2, privateVariable){ function doSomethingPrivate() { d1.doAction(privateVariable); } this.doSomething = function (p){ privateVariable = p; doSomethingPrivate(); }; } this.$get = [‘dependency1', ‘dep2', function (d1, d2){ return new MyServiceConstructor(d1, d2, privateVarConfig); }]; }); Services decoration • Внедряется в процесс инстанциирования сервиса • Позволяет изменить или расширить функциональность сервиса • Реализуется с помощью сервиса $provide • Можно декорировать value, service, factory, provider • Определяется в конфиге модуля: angular.module(‘myApp', [‘myModule’]) .config([‘$provide’, function(provide){ provide.decorator(‘myService’, [‘$delegate’, function(d){ d.anotherFunction = function(){}; return d; }]); }]); Основные сервисы angularjs • $cacheFactory – работа с memory cache • $compile – компеляция темплейтов и их связка со scope • $document, $window – обертки над соответствующими объектами браузера • $http – коммуникация с удаленным сервером посредством XMLHttpRequest или JSONP • $injector – контейнер зависимостей • $interval, $timeout – обертки над setInterval() и setTimeout() функциями объекта window • $location – работа со строкой адреса браузера • $q – помогает работать с асинхронными функциями $q this.doSomethingAsync = function(){ var deferred = $q.defer(); //run async operation ($http, $timeout, etc) //call deferred.resolve(result) if success //call deferred.reject(reason) if error return deferred.promise; }; ---------------------------------------------------function successCallback(result){/* handle result */} function errorCallback(reason) {/* handle error */} myService.doSomethingAsync().then(successCallback, errorCallback); ---------------------------------------------------this.getDataAsync = function(inputParam){ if(!inputParam){ return $q.reject(‘Incorrect input param’); } if(hasData(inputParam)){ return $q.when(getData(inputParam)); } return $http.get(‘some/url/’+inputParam).then(function(data){ cacheData(inputParam, data); return getData(inputParam); }); } Directives • Реализуют UI логику • Определяются: angular.module(‘myModule’, []) .directive(‘myDirective’, [function () { return { restrict: ‘AE’, //CM templateUrl: ‘some/template/path.html’, scope: { someTextParam: ‘@title’, someObjectParam: ‘=’ }, link: function ($scope, $element, $attrs) { } }; }]); • Используются: <div my-directive title=“Title: {{ title }}” some-object-param=“obj” /> <my-directive title=“Title: {{ title }}” some-object-param=“obj” /> Основные директивы angularjs • Обработка событий DOM в контексте angular digest ng-blur, ng-change, ng-click, ng-copy, ng-cut, ng-dblclick, ng-focus, ngkeydown, ng-keypress, ng-mousedown, ng-mouseenter, ng-mouseleave, ng-mousemove, ng-mouseover, ng-mouseup, ng-paste, ng-submit • Предотвращение не валидного запроса из-за использования биндингов ng-href, ng-src, ng-srcset <img src=“http://www.some.site.com/path/{{urlPart}}.jpg” /> <img ng-src=“http://www.some.site.com/path/{{urlPart}}.jpg” /> • Генерация элементов ng-if, ng-include, ng-repeat, ng-switch • Управление стилями элементов ng-class, ng-class-even, ng-class-odd, ng-cloak, ng-hide, ng-show, ng-style • Управление состоянием элементов ng-checked, ng-disabled, ng-readonly, ng-selected Filters • Предназначены для преобразования значений модели в удобный для пользователя вид • Определяются: angular.module(‘myModule’, []) .filter(‘myFilter’, [function () { return function (value, param1, param2) { }; }]); • Используются: <p ng-bind=“someValue | myFilter : ‘param1’” /> • Фильтры в составе Angularjs: • currency (symbol, fractionSize) • date (format, timezone) • filter (expression, comparator), orderBy (expression, reverse) • json (spacing) • limitTo (limit) • lowercase, uppercase • number (fractionSize) ngRoute • Модуль, преднозначеный для реализации навигации • Состоит из директивы ngView и сервисов $route и $routeParams • Определение роутов: angular.module(‘myApp’, [‘ngRoute’]) .config([‘$routeProvider’, ‘$locationProvider’, function ($routeProvider, $locationProvider) { $locationProvider.html5Mode(true); $routeProvider.when(‘/some/url’, { templateUrl: ‘path/to/view/template.html’, controller: ‘viewControllerName’ }); $routeProvider.otherwise(‘/home/page’); }]); Demo Рекомендуемые материалы • “AngularJS: Up And Running”, Shyam Seshadri and Brad Green, O’Reilly, September 2014, First Edition • https://www.youtube.com/user/angularjs • https://docs.angularjs.org/guide • https://docs.angularjs.org/api • https://angular-ui.github.io/ • UI-Bootstrap • UI-Router • UI-Grid • UI-Gmap Спасибо за внимание Вопросы и ответы