- PVSM.RU - https://www.pvsm.ru -
Часть 1 [1]
$rootScope не сильно отличается от $scope, просто это объект $scope самого верхнего уровня, от которого происходят все остальные области видимости. Когда Angular начинает создание вашего приложение, он создаёт объект $rootScope, и все привязки и логика приложения создают объекты $scope, являющиеся наследниками $rootScope.
Обычно мы не используем $rootScope, но с его помощью можно обеспечить передачу данных между разными областями видимости.
Контроллер позволяет взаимодействовать Виду и Модели. Это то место, где логика презентации синхронизирует интерфейс с моделью. Цель Контроллера – приводить в действие изменения в Модели и Виде. В Контроллере Angular сводит вместе бизнес-логику и логику презентации.
Мы уже касались Контроллеров, объявив атрибут ng-controller для показа данных $scope. Этот атрибут связывает область видимости и экземпляр Контроллера, и обеспечивает доступ к данным и методам Контроллера из DOM.
Перед использованием Контроллера его надо создать. Воспользуемся уже пройденным материалом:
angular
.module('app', [])
.controller('MainCtrl', function () {
});
Контроллер принимает два аргумента – имя, по которому на него можно ссылаться, и функцию обратного вызова. При этом на самом деле это будет функция, описывающая тело Контроллера.
Чтобы методы Angular не выглядели, как обратные вызовы, я размещаю функции вне синтаксиса Angular.
function MainCtrl () {
}
angular
.module('app', [])
.controller('MainCtrl', MainCtrl);
Так оно выглядит почище и читается легче. Я назвал функцию MainCtrl, потому что именованные функции легче отлаживать.
Последующие примеры подразумевают, что модуль был создан.
Цель Контроллера – интерпретировать бизнес-логику модели и преобразовывать её в формат презентации. В Angular это можно делать по-разному, в зависимости от того, какие мы получаем данные.
Контроллер общается с Сервисом, и передаёт данные в том же, или изменённом формате в наш Вид через объект $scope. Когда Вид обновлён, логика Контроллера также обновляется, и её можно передавать обратно на сервер через Сервис. Но сначала мы создадим несколько объектов в Контроллере и свяжем их со $scope, чтобы разобраться, как работает Контроллер. Бизнес-логику мы расписывать не будем, для краткости мы добавим её ниже и получим данные от Сервиса.
function MainCtrl ($scope) {
$scope.items = [{
name: 'Набор ныряльщика',
id: 7297510
},{
name: 'Шноркель',
id: 0278916
},{
name: 'Гидрокостюм',
id: 2389017
},{
name: 'Полотенце',
id: 1000983
}];
}
angular
.module('app')
.controller('MainCtrl', MainCtrl);
При помощи $scope мы привязываем массив. После этого он будет доступен в DOM, где его можно передать одной из встроенных Директив Angular, ng-repeat, чтобы в цикле пройтись по данным и создать структуру в DOM, основанную на шаблоне и данных.
<div ng-controller="MainCtrl">
<ul>
<li ng-repeat="item in items">
{{ item.name }}
</li>
</ul>
</div>
Контроллеры похожи на классы, но использование их через объекты $scope не похоже на использование классов. Они предлагали использовать ключевое слово this вместо $scope. Разработчики Angular ввели такую возможность в рамках синтакса controllerAs, где Контроллер оформляется в виде экземпляра, хранящегося в переменной – примерно так же, как использование new с переменной для создания нового объекта.
Мы опускаем вызов $scope и используем this.
function MainCtrl () {
this.items = [{
name: 'Набор ныряльщика',
id: 7297510
},{
name: 'Шноркель',
id: 0278916
},{
name: 'Гидрокостюм',
id: 2389017
},{
name: 'Полотенце',
id: 1000983
}];
}
angular
.module('app')
.controller('MainCtrl', MainCtrl);
Потом мы добавляем “as” там, где нам нужно создать экземпляр Контроллера в DOM.
Запись MainCtrl as main означает, что все данные находятся в переменной main, поэтому items из прошлого примера превращаются в main.items.
<div ng-controller="MainCtrl as main">
<ul>
<li ng-repeat="item in main.items">
{{ item.name }}
</li>
</ul>
</div>
Так понятнее, к какому Контроллеру принадлежит свойство, когда у нас есть много Контроллеров или вложенные Контроллеры. К тому же, это помогает избежать конфликт имён.
У каждого создаваемого $scope есть объект $parent. Без использования controllerAs нам надо бы было использовать ссылки на $parent для любых методов из области видимости $parent, и $parent.$parent для области на уровень выше, и так далее. Теперь же мы можем просто использовать имя переменной.
Сервисы позволяют хранить данные Модели и бизнес-логику, например, общение с сервером через HTTP. Часто путают Сервисы и Фабрики – отличие их в том, как создаются соответствующие объекты.
Важно помнить, что все Сервисы – синглтоны, на каждую инъекцию есть только один Сервис. По соглашению имена Сервисам даются в стиле паскаля, то есть «my service» пишется как «MyService».
6.1 Метод service
Метод создаёт новый объект, который общается с бэкендом и предоставляет инструменты для работы с бизнес-логикой. Сервис – это объект constructor, который вызывают через ключевое слово new, в связи с чем наша логика связывается с сервисом при помощи ключевого слова this. Сервис создаёт объект-синглтон.
function UserService () {
this.sayHello = function (name) {
return 'Привет тебе ' + name;
};
}
angular
.module('app')
.service('UserService', UserService);
Теперь можно вставлять Сервис в Контроллер.
function MainCtrl (UserService) {
this.sayHello = function (name) {
UserService.sayHello(name);
};
}
angular
.module('app')
.controller('MainCtrl', MainCtrl);
Перед Сервисом нельзя выполнять код, поскольку все методы создаются в виде объектов. У Фабрики всё обстоит иначе.
Фабричные методы возвращают объект или функцию, поэтому мы можем использовать замыкания, или возвращать объект host, к которому можно привязывать методы. Можно создавать приватную и публичную области видимости. Все Фабрики становятся Сервисами, поэтому мы так их и называем.
Мы воссоздадим UserService из примера, для сравнения используя фабричный метод.
function UserService () {
var UserService = {};
function greeting (name) {
return 'Привет тебе ' + name;
}
UserService.sayHello = function (name) {
return greeting(name);
};
return UserService;
}
angular
.module('app')
.factory('UserService', UserService);
При помощи замыканий можно эмулировать приватную область видимости. Можно было бы создать что-то подобное и внутри метода service constructor, но здесь видно, что возвращается, а что остаётся внутри области видимости Сервиса. Так же можно создать приватные вспомогательные функции, которые остаются в области видимости, когда функция уже вернулась, при этом их можно использовать из публичных методов. Внутри Контроллера используются такие Сервисы точно так же.
function MainCtrl (UserService) {
this.sayHello = function (name) {
UserService.sayHello(name);
};
}
angular
.module('app')
.controller('MainCtrl', MainCtrl);
Сервисы обычно используют не для логики презентаций, а для слоя бизнес-логики. Это общение с бэкендом через REST по Ajax.
Пока мы рассматривали программисткую сторону Angular, но не показывали, как же его можно использовать из HTML. Следующий шаг – использовать мощные возможности шаблонизации.
Выражения Angular – это похожие на JavaScript сниппеты, которые можно использовать в шаблонах, чтобы проводить условные изменения в DOM – как в элементах, так и их свойствах, а также в тексте. Они живут внутри ссылок {{}} и выполняются в пределах $scope. Там нет циклов или if/else, и мы не можем выбрасывать исключения. Можно делать только мелкие операции или вызывать значения свойств $scope.
К примеру, {{ value }} – это выражение. В выражениях можно использовать логические операторы вроде || и &&, или тернарный оператор value? true: false.
С их помощью можно создавать гибкие шаблоны и обновлять переменные без перезагрузки страниц.
function MainCtrl () {
this.items = [{
name: 'Набор ныряльщика',
id: 7297510
},{
name: 'Шноркель',
id: 0278916
},{
name: 'Гидрокостюм',
id: 2389017
},{
name: 'Полотенце',
id: 1000983
}];
}
angular
.module('app')
.controller('MainCtrl', MainCtrl);
Можно получить длину массива, объявив выражение, использующее свойство length.
<div ng-controller="MainCtrl as main">
{{ main.items.length }} шт. в наличии
</div>
Angular заменит length на значение, и мы увидим “4 шт. в наличии”
После такого использования значения автоматически связываются внутри Angular, поэтому любые обновления влияют на значение выражений. Если мы уберём из массива один предмет, внешний вид автоматически обновится до “3 шт. в наличии”. Никакой возни, никаких лишних обратных вызовов.
Есть целая куча директив с префиксом ng-*. Начнём с ng-click и привяжем функцию к новому кусочку HTML-темплейта. В примере я прохожу по массиву и показываю количество товара в наличии.
<div ng-controller="MainCtrl as main">
<div>
{{ main.items.length }} шт. в наличии
</div>
<ul>
<li ng-repeat="item in main.items" ng-click="main.removeFromStock(item, $index)">
{{ item.name }}
</li>
</ul>
</div>
Атрибут ng-click связывается с функцией main.removeFromStock(). Я передаю ей товар, который в данный момент мы обрабатываем в цикле. Свойство $index полезно для удаления элементов из массива – нам не нужно вручную подсчитывать индекс текущего элемента.
Теперь функцию можно добавить к Контроллеру. В функцию передаются $index и элемент массива, и метод делает следующее:
function MainCtrl () {
this.removeFromStock = function (item, index) {
this.items.splice(index, 1);
};
this.items = [...];
}
angular
.module('app')
.controller('MainCtrl', MainCtrl);
При создании методов надо учитывать, что значение this может разниться, в зависимости от использования и контекста выполнения. Обычно я создаю ссылку на Контроллер как
var vm = this;
где vm – это ViewModel. При таком подходе ссылки не потеряются. Переработанный контроллер выглядит так:
function MainCtrl () {
var vm = this;
vm.removeFromStock = function (item, index) {
vm.items.splice(index, 1);
};
vm.items = [...];
}
angular
.module('app')
.controller('MainCtrl', MainCtrl);
Так и создаётся логика презентации, работающая с интерфейсом. Пропущен лишь один шаг – обновление Модели. Перед удалением элемента из vm.items мы должны отправить запрос DELETE на бэкенд, и после успешного ответа удалить его из массива. Тогда DOM обновится только в случае успешной обработки запроса и не введёт пользователя в заблуждение.
Хотя движок шаблонов достаточно мощный, всегда нужно помнить о возможностях повторного использования кода в будущих программах. Для этого хорошо подходят Директивы.
Есть два вида Директив – одни работают со связками внутри Angular, а другие мы создаём сами. Директива может делать, что угодно – предоставлять логику для заданного элемента, или сама быть элементом и предоставлять шаблон с логикой внутри себя. Идея Директив в расширении возможностей HTML.
Посмотрим на встроенные директивы Angular, затем попробуем создать свои.
Мы уже встречались с ней, поэтому я дам только простой пример.
<ul>
<li ng-repeat="item in main.items">
{{ item }}
</li>
</ul>
ng-repeat копирует элемент и воспроизводит его, заполняя при этом данными объектов из массива. Если из массива удалить элемент, DOM обновится автоматически.
Используется для инициализации новой несуществующей модели, или для привязки существующей.
Сообщение: {{ main.message }}
Если в свойстве $scope main.message содержится значение, оно будет передано в input. Если в $scope нет такого значения, оно просто будет проинициализировано. Мы можем передавать эти значения в другие директивы, например в ng-click.
Прелесть его в том, что нам не надо самим навешивать обработчики событий. Angular сам подсчитает значение выражения внутри директивы и повесит обработчик на событие. В отличие от onClick=’’, директива ng-click принадлежит к своей области видимости, т.е. не является глобальной.
<input type="text" ng-model="main.message">
<a href=" ng-click="main.showMessage(main.message);">Показать сообщение</a>
Здесь я передаю main.message в метод main.showMessage, а там Angular обрабатывает его как простой объект JavaScript. В этом состоит прелесть Angular – все связки данных в DOM являются объектами, мы можем просто парсить их, манипулировать ими, преобразовывать в JSON и отправлять на бэкенд.
Чтобы Angular сам заботился об особенностях работы браузеров с параметрами href и src, вместо них мы используем ng-href=" and ng-src=".
<a ng-href="{{ main.someValue }}">Go</a>
<img ng-src="{{ main.anotherValue}}" alt=">
Эта Директива выглядит как описание свойств и значений объекта. Вместо традиционных вызовов elem.addClass(className) и elem.removeClass(className), Angular добавляет и удаляет классы на основании заданных выражений.
<div class="notification" ng-class="{
warning: main.response == 'error',
ok: main.response == 'success'
}">
{{ main.responseMsg }}
</div>
Angular выясняет значение main.response и в зависимости от него добавляет класс warning или ok.
Эти Директивы часто встречаются при использовании Angular. Это удобный способ показывать и прятать данные в зависимости от значения свойства.
Для переключения видимости элемента мы используем ng-click.
<a href=" ng-click="showMenu = !showMenu">Переключить меню!</a>
<ul ng-show="showMenu">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
Разница только в том, что ng-show или ng-hide определяют, должен ли элемент изначально быть видимым или быть спрятанным.
ng-if не просто прячет элементы, а удаляет их. Если элемент нужно воссоздать после изменения значения свойства, для него создаётся новый $scope. Это также положительно влияет на быстродействие фреймворка.
<div ng-if="main.userExists">
Введите логин
</div>
Директива, сходная с оператором case / switch в программировании, или продвинутый ng-if. В зависимости от значения из $scope выбирается один из нескольких элементов.
<div ng-switch on="main.user.access">
<div ng-switch-when="admin">
<!-- code for admins -->
</div>
<div ng-switch-when="user">
<!-- code for users -->
</div>
<div ng-switch-when="author">
<!-- code for authors -->
</div>
</div>
Значения можно вставлять в DOM через синтаксис {{ value }}, но есть ещё один вариант, ng-bind. Различия синтаксиса видно на примере.
<p>{{ main.name }}</p>
<p ng-bind="main.name"></p>
ng-bind можно использовать, чтобы избежать мигания при загрузке страницы. Angular автоматически скрывает контент, который подгружается через Директивы. При использовании фигурных скобок они могут быть видны в тексте документа. При использовании ng-bind вставки не видны, пока Angular не вычислит нужные значения.
Одностраничные приложения используют одну страницу без перезагрузок, содержимое которой обновляется автоматически. Это достигается использованием атрибута ng-view с пустым элементом вроде <div></div>, который служит контейнером для любого динамически вставляемого кода, получаемого через XMLHttpRequest.
На место ng-view вставляются различные Виды, в зависимости от пути в URL. Например, можно сказать Angular, чтобы он вставлял login.html, если URL содержит myapp.com/#/login, и менять содержимое при изменении URL.
Хотя встроенные Директивы добавляют функциональности в HTML, иногда необходимо добавить свои функции для дальнейшего расширения возможностей. Рассмотрим API для создания своих директив.
Настраиваемые Директивы – одна из самых сложных концепций в API Angular, потому что они не похожи на привычные программные концепции. Они выступают в роли способа Angular реализовать концепции Веб ближайшего будущего – настраиваемые элементы, теневой DOM, шаблоны и импорт HTML. Давайте постепенно изучим Директивы, разбив их на слои.
НЭ используются в случаях повторного использования кода или шаблонного кода, когда мы можем объявить один элемент, а код, связанный с ним, будет автоматически присоединён к нему.
В Angular есть четыре способа использования Директив – Настраиваемые элементы, Настраиваемые атрибуты, имена классов и комментарии. Последних двух я стараюсь избегать, потому что в них легко запутаться, и к тому же, у комментариев есть проблемы с IE. Самый безопасный и кроссбраузерный способ – Настраиваемые атрибуты. Давайте рассмотрим эти способы в следующем порядке: Element, Attribute, Class, Comment.
<my-element></my-element>
<div my-element></div>
<div class="my-element"></div>
<!-- directive: my-element -->
У Директив есть свойство restrict, через которое можно ограничить их использование одним из этих способов. По умолчанию, Директивы используются через ‘EA’, что означает Element, Attribute. Другие варианты – C для классов и М для комментариев.
Теневой DOM работает так, что внутри определённых частей обычного DOM документа содержатся вложенные документы. Они поддерживают HTML, CSS и области видимости JavaScript.
В Теневом DOM можно определять как чистый HTML, так и контент, который будет в него импортирован. Мы можем поместить текст в Настраиваемый элемент:
<my-element>
Всем привет!
</my-element>
И текст «Всем привет!» доступен в Теневом DOM.
Существует три различных способа использования шаблонов в Директивах. Два почти одинаковых, а третий хранит шаблон в строке.
Объявляет нужный шаблон. Шаблон форматируется как строка, а затем Angular компилирует его и вставляет в DOM
Пример:
{
template: '<div>' +
'<ul>' +
'<li ng-repeat="item in vm.items">' +
'{{ item }}' +
'</li>' +
'</ul>' +
'</div>'
}
При использовании такого подхода удобно вписывать логику на JavaScript между строками. Я использую конструкцию с [].join(''), она делает текст более читаемым.
{
template: [
'<div>',
'<ul>',
'<li ng-repeat="item in vm.items">',
'{{ item }}',
'</li>',
'</ul>',
'</div>'
].join('')
}
Свойство templateUrl позволяет указывать на внешний ресурс или на элемент <script>, которые содержат нужный шаблон.
Если задать:
{
templateUrl: 'items.html'
}
Angular сначала поищет в DOM элемент <script> с подходящим id, а если не найдёт, тогда запросит документ через HTTP GET.
<script type="text/ng-template" id="/hello.html">
<div>
<ul>
<li ng-repeat="item in vm.items">
{{ item }}
</li>
</ul>
</div>
</script>
Сначала мы задаём тип шаблона как text/ng-template, чтобы браузер не интерпретировал его текст, как JavaScript. Таким образом можно включать текст шаблонов в файл, а не скачивать его отдельными файлами. Также можно использовать свойство template, когда шаблон хранится в строке. Когда шаблон загружается, Angular сохраняет его, и потом его можно использовать в ng-include и ng-view. В ином случае Angular осуществит GET-запрос файла с шаблоном.
Все загруженные шаблоны хранятся в $templateCache в течение всей жизни приложения.
Давайте поподробнее рассмотрим API Директив, чтобы можно было создавать свои директивы. Первая инструкция – return, возвращающая объект. Это всё, что нужно для создания директивы.
function someDirective () {
return {
};
}
angular
.module('app')
.controller('someDirective', someDirective);
Посмотрим на самые популярные свойства объектов директив.
function someDirective () {
return {
restrict: 'EA',
replace: true,
scope: true,
controllerAs: 'something',
controller: function () {
},
link: function ($scope, $element, $attrs) {
},
template: [
'<div class="some-directive">',
'My directive!',
'</div>'
].join('')
};
}
angular
.module('app')
.controller('someDirective', someDirective);
Мы уже упоминали restrict – она позволяет ограничивать использование директивы. Если мы хотим задавать директиву только через атрибуты, можно ограничить их ‘A’. Для ограничения работы директив в качестве элементов используется ‘E’, комментариев – ‘M’ и имён классов – ‘C’.
Заменяет оригинальный элемент директивы. Если мы используем <some-directive></some-directive> и задаём replace: true, после создания страницы изначальный элемент будет заменён результатом работы скрипта.
Позволяет наследовать $scope текущего или родительского контекста, в который входит директива. Можно создать изолированный $scope и передавать определённые значения, обычно через настраиваемые атрибуты.
Мы уже пробовали это свойство, которое определяет имя контроллера внутри директивы. Если мы задаём controllerAs: 'something', то все ссылки на свойства контроллера будут выглядеть как something.myMethod()
Захватить существующий контроллер или создать новый. Если MainCtrl уже существует, можно определить его как controller: 'MainCtrl'. Для сохранения инкапсуляции мы просто объявляем новый контроллер каждый раз через controller: function () {}. Функция обратного вызова контроллера должна обрабатывать изменения в ViewModel и общаться с Сервисами.
Функция link вызывается после того, как элемент компилируется и вставляется в DOM, поэтому здесь можно сделать что-то с контентом после компиляции или что-то, не связанное с Angular.
В контроллере мы не манипулируем DOM, но это возможно в функции link. Она также может вставлять $scope, корневой элемент шаблона $element и объект $attrs, содержащий все свойства элемента DOM, отражающего текущее состояние {{ }}. Внутри link можно привязать обработчики событий, определить плагины и даже вставить сервисы Angular.
Пройдёмся по примеру создания директивы, которая позволяет вставлять компонент “email”, у которого есть поля To, Subject и Message.
Мы создаём элемент <compose-email>, на место которого происходит вставка контента. Его можно вставить в разные места DOM многократно, и везде он будет заменён на наш компонент, каждый из которых будет отдельным экземпляром. Начнём с шаблонов.
function composeEmail () {
return {
restrict: 'EA',
replace: true,
scope: true,
controllerAs: 'compose',
controller: function () {
},
link: function ($scope, $element, $attrs) {
},
template: [
'<div class="compose-email">',
'<input type="text" placeholder="To..." ng-model="compose.to">',
'<input type="text" placeholder="Subject..." ng-model="compose.subject">',
'<textarea placeholder="Message..." ng-model="compose.message"></textarea>',
'</div>'
].join('')
};
}
angular
.module('app')
.controller('composeEmail', composeEmail);
Теперь мы можем использовать директиву composeEmail в нескольких местах, без необходимости копировать куски HTML. Помните, что Angular парсит название директивы так, что composeEmail превращается в <compose-email></compose-email>
Фильтры обрабатывают информацию и выдают определённые наборы данных, основываясь на какой-либо логике. Это может быть что угодно, от форматирования даты в читаемый формат, до списка имён, которые начинаются на заданную букву. Посмотрим на популярные встроенные фильтры.
Их можно использовать либо в DOM через символ вертикальной черты | в выражениях, которые парсит Angular, либо через сервис $filter, который можно использовать в JavaScript коде вместо HTML.
HTML синтакс:
{{ filter_expression | filter : expression : comparator }}
JavaScript синтакс:
$filter('filter')(array, expression, comparator);
Чаще используется более простой вариант с HTML. Вот некоторые из фильтров.
С датами работать муторно, но Angular облегчает эту задачу. Привяжем фильтр к дате в миллисекундах к $scope.timeNow = new Date().getTime():
<p>
Сегодня у нас: {{ timeNow | date:'dd-MM-yyyy' }}
</p>
Встроенные фильтры JSON преобразуют объект JavaScript в строку JSON. Это удобно для вывода значений Модели в DOM при разработке. Для красивого форматирования оберните фильтры JSON в тэги <pre>
<pre>
{{ myObject | json }}
</pre>
Описанные фильтры работают просто – получают значение и выдают значение. Как же работать с наборами данных?
limitTo ограничивает количество данных, передаваемых в Вид – его удобно использовать внутри ng-repeat.
<ul>
<li ng-repeat="user in users | limitTo:10">
{{ user.name }}
</li>
</ul>
Фрагмент выведет не более 10 пользователей. Обратите внимание на использование limitTo внутри ng-repeat.
orderBy задаёт порядок вывода массива, сортируя по одному из свойств объектов. Если у нас объект пользователя выглядит так:
{
name: 'Todd Motto',
}
мы можем вывести лист по алфавиту следующим образом:
<ul>
<li ng-repeat=" user in users | orderBy:'name' ">
{{ user.name }}
</li>
</ul>
Настоящие возможности фильтров раскрываются при создании своих собственных.
Мы все делали фильтры в объектах и массивах. Для использования их в Angular существует API. Мы имеем двустороннюю связь данных безо всякого труда. Все заданные нами фильтры будут вызваны в цикле $digest.
Принимают одно значение, и выдают отфильтрованный контент. Для создания используется метод .filter(). Все такие фильтры глобально доступны в любой области видимости.
Вот заготовка фильтра, от которой можно отталкиваться.
function myFilter () {
return function () {
// возврат результата
};
}
angular
.module('app')
.filter('myFilter', myFilter);
Все аргументы для фильтров передаются внутрь автоматически. Для примера сделаем фильтр форматирования текста в нижний регистр (хотя у Angular есть такой встроенный фильтр).
function toLowercase () {
return function (item) {
return item.toLowerCase();
};
}
angular
.module('app')
.filter('toLowercase', toLowercase);
item передаётся фильтру как локальная переменная. Используется фильтр точно так же, как встроенные:
<p>{{ user.name | toLowercase }}</p>
Иногда надо обработать набор данных и вернуть их преобразованными. Давайте сделаем фильтр, который отбирает имена по заданной первой букве. Например, вот фильтр слов, начинающихся с 'A'.
function namesStartingWithA () {
return function (items) {
return items.filter(function (item) {
return /$a/i.test(item.name);
});
};
}
angular
.module('app')
.filter('namesStartingWithA', namesStartingWithA);
Используем его внутри ng-repeat:
<ul>
<li ng-repeat="item in items | namesStartingWithA">
{{ item }}
</li>
</ul>
Передавать аргументы в фильтры можно через двоеточие:
<ul>
<li ng-repeat="item in items | namesStartingWithA:something">
{{ item }}
</li>
</ul>
something автоматически передаётся в функцию фильтра:
function namesStartingWithA () {
return function (items, something) {
// есть доступ и к "items", и к "something"
};
}
angular
.module('app')
.filter('namesStartingWithA', namesStartingWithA);
Фильтры можно создавать вне метода .filter(), просто передавая функцию в контроллер, которая будет выполнять роль фильтра. Продолжая предыдущий пример, мы можем создать функцию контроллера this.namesStartingWithA, тогда фильтр будет доступен только из этого контроллера, а не глобально. В этом случае используется синтакс controllerAs.
function SomeCtrl () {
this.namesStartingWithA = function () {
};
}
angular
.module('app')
.controller('SomeCtrl', SomeCtrl);
Синтакс вызова фильтра в DOM слегка отличается:
<ul>
<li ng-repeat="item in vm.items | filter:namesStartingWithA">
{{ item }}
</li>
</ul>
Мы разбираемся с разными концепциями Angular, но пока не дошли до главной: как же Angular помогает создать одностраничное приложение. А для этого используется роутер, который мы настраиваем так, чтобы он менял состояние нашего приложения в зависимости от URL. Нужный нам роутер называется ngRoute
angular
.module('app', [
'ngRoute'
]);
Подключив таким образом нужный модуль, мы можем настраивать пути, вставляя $routeProvider и настраивая его в методе .config(). После этого мы получаем доступ к методу .when().
Представим, что у меня есть приложение для чтения почты – по нажатию на «входящие» /inbox нужно, чтобы оно показывало список писем. Тогда первый аргумент в .when() будет строкой, описывающей нужный URL. Второй аргумент – объект с дополнительными настройками. Есть ещё метод .otherwise(), который обрабатывает случаи несуществующих путей.
function router ($routeProvider) {
$routeProvider
.when('/inbox', {})
.otherwise({
redirectTo: '/inbox'
});
}
angular
.module('app')
.config(router);
Для настройки пути сначала необходимо выбрать шаблон, который будет использоваться с нужным Видом. Предположим, мы используем шаблон inbox.html.
$routeProvider
.when('/inbox', {
templateUrl: 'views/inbox.html'
})
.otherwise({
redirectTo: '/inbox'
});
Всем Видам необходим Контроллер. У Angular для этого есть свойства controller и controllerAs.
$routeProvider
.when('/inbox', {
templateUrl: 'views/inbox.html',
controller: 'InboxCtrl',
controllerAs: 'inbox'
})
.otherwise({
redirectTo: '/inbox'
});
Теперь допустим, мы хотим настроить ещё один Вид – когда мы щёлкаем по входящему письму, мы должны увидеть само письмо. Для этого необходим динамический роутинг, поскольку у разных писем должны быть разные URL (содержащие id и т.п.). Для передачи данных динамического роутинга используется двоеточие перед названием динамической группы. Например, в случае письма с id 173921938 путь будет /inbox/email/173921938, а описание этого пути — '/inbox/email/:id'.
Когда приложение получает путь /inbox/email/173921938, Angular загружает те же самые шаблон и контроллер, что и для /inbox/email/902827312.
Итоговый пример выглядит так:
function router ($routeProvider) {
$routeProvider
.when('/inbox', {
templateUrl: 'views/inbox.html',
controller: 'InboxCtrl',
controllerAs: 'inbox'
})
.when('/inbox/email/:id', {
templateUrl: 'views/email.html',
controller: 'EmailCtrl',
controllerAs: 'email'
})
.otherwise({
redirectTo: '/inbox'
});
});
angular
.module('app')
.config(router);
Это основа для приложения, использующего пути. Но нужно ещё указать, куда на странице необходимо вставлять обработанный шаблон. Для этого используется ng-view. Обычно достаточно написать что-то вроде:
<div ng-view></div>
Все изменения URL обрабатываются Angular, который определяет, не нужно ли вставлять в страницу другой шаблон. Вот и всё, что требуется для работы приложения.
Сервис $routeParams автоматически парсит URL и выделяет из него набор параметров, который преобразовывает в объект. Например, в последнем примере будет извлечён id письма на основании задания динамического роутинга через :id
Простой пример контроллера, передающего параметры из $routeParams:
function EmailCtrl ($routeParams, EmailService) {
// $routeParams { id: 20999851 }
EmailService
.get($routeParams.id) // передать объект
.success(function (response) {})
.error(function (reason) {});
}
angular
.module('app')
.('EmailCtrl', EmailCtrl);
Проверка форм осуществляет несколько действий, от проверки изменений Модели и сверки их с существующими правилами связки, до изменения DOM для обратной связи с пользователем.
Для создания такой формы необходимо задать ей имя, определяющее область видимости формы.
<form name="myForm"></form>
Angular распознает такую форму и будет проверять и отслеживать ввод пользователя, например, заполнил ли тот обязательные поля формы, и т.п.
В HTML5 был добавлен атрибут pattern, который позволял браузеру проверять ввод согласно с заданной регуляркой. Angular включает подобные возможности. Кроме этого, Angular переработал проверку полей, помеченных как required в своей директиве под названием ng-required, которая тоже постоянно отслеживает состояние Модели. Давайте посмотрим ещё на несколько возможностей Angular.
После первоначального создания страницы Angular добавляет к форме свойство $pristine – это означает, что пользователь к ней ещё не притрагивался. Angular добавит класс ng-pristine к тем элементам, которые ещё не менялись.
Противоположность pristine – это элементы, которые пользователь изменил. К ним добавляются классы ng-dirty, а классы ng-pristine удаляются. Форма не может вернуться к состоянию $pristine без перезагрузки страницы.
Каждое поле ввода может быть объявлено $valid. Например, если у поля есть атрибут ng-required, а пользователь заполнил его, то ему присваивается класс ng-valid
Противоположность valid. По умолчанию, все формы находятся в состоянии $invalid – им присвоен класс ng-invalid. Переходы между этими двумя состояниями происходят по мере того, как пользователь вводит информацию.
В некоторых случаях нужно отключать или включать поля ввода или кнопки – например, пока пользователь что-то не введёт. Простой пример переключения состояний кнопки, основанный на данных Модели. Если пользователь не ввёл имя, форму нельзя обновлять.
<input type="text" ng-model="user.name" placeholder="Введите имя">
<button ng-disabled="!user.name.length">
Обновить имя
</button>
Если user.name.length возвращает true, кнопка включается. И это состояние постоянно отслеживается всё время, пока происходит работа с формой.
Для связи с сервером в Angular есть два API, $http и $resource. Это высокоуровневые способы связи с сервером, которые также запускают циклы $digest, поддерживая наши данные в актуальном состоянии.
Если вы пользовались методом jQuery $.ajax, то вам всё сразу будет понятно. Метод $http можно использовать как функцию и как объект. Мы можем передать ему объект с настройками $http({...}), или же использовать доступные методы вроде $http.get(...). Метод $http основан на API обещаний, которое предоставляется встроенным сервисом $q.
Мне больше нравятся методы с короткой записью, поэтому для демонстрации работы я буду использовать именно их. Вот простой HTTP-запрос GET:
$http.get('/url')
.success(function (data, status, headers, config) {
})
.error(function (data, status, headers, config) {
});
Успешные и неудавшиеся обратные вызовы происходят асинхронно, и в ответе содержатся данные, статус и заголовки. Обычно все сразу они нам не нужны, поэтому можно использовать простой подход:
$http.get('/url')
.success(function (response) {
})
.error(function (reason) {
});
Мне нравятся использовать шаблоны обещаний при помощи метода .then(), поддерживающегося в Angular:
$http.get('/url')
.then(function (response) {
// успех
}, function (reason) {
// провал
});
В отличие от библиотеки jQuery, Angular оборачивает вызовы $http в $scope.$apply(), что запускает цикл запроса данных с сервера и обновляет связи.
Вместе с $http можно использовать модуль ngResource. В нём есть API $resource, удобное для CRUD-операций (create, read, update, delete). $resource создаёт объект, через который можно общаться с источниками данных по протоколам REST.
function MovieService ($resource) {
return $resource('/api/movies/:id', { id: '@_id' },
{
update: {
method: 'PUT'
}
}
);
}
angular
.module('app')
.factory('MovieService', MovieService);
Можно вставить зависимость в простую фабрику Movies в наши контроллеры и получать таким образом все нужные данные.
function MovieCtrl (MovieService) {
var movies = new MovieService();
// произошло обновление фильма
movies.update(/* some data */);
}
angular
.module('app')
.controller('MovieCtrl', MovieCtrl);
Автор: SLY_G
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/78628
Ссылки в тексте:
[1] Часть 1: http://habrahabr.ru/post/246881/
[2] Источник: http://habrahabr.ru/post/247283/
Нажмите здесь для печати.