Главное в AngularJS заключается в том, что он позволяет нам расширять возможности HTML для удовлетворения потребностей современных динамических веб-страниц. В этой статье я покажу вам, как вы можете использовать Директивы AngularJS , чтобы сделать вашу разработку быстрее, проще и сделать ваш код более понятным.
подготовка
Шаг 1: HTML-шаблон
Для упрощения мы напишем весь наш код в одном файле HTML. Создайте его и поместите в него базовый HTML-шаблон:
1
|
<!DOCTYPE html> <html> <head> </head> <body> </body> </html>
|
Теперь добавьте файл angular.min.js
из CDN Google в <head>
документа:
1
|
<script src=»https://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.min.js»></script>
|
Шаг 2: Создание модуля
Теперь давайте создадим модуль для наших директив. Я назову это примером , но вы можете выбрать все, что захотите, просто помните, что мы будем использовать это имя в качестве пространства имен для директив, которые мы создадим позже.
Поместите этот код в тег скрипта внизу <head>
:
1
|
var module = angular.module(‘example’, []);
|
У нас нет никаких зависимостей, поэтому массив во втором аргументе angular.module()
пуст, но не удаляйте его полностью, иначе вы получите ошибку $ injector: nomod , потому что angular.module с одним аргументом angular.module()
возвращает ссылку на уже существующий модуль вместо создания нового.
Вы также должны добавить атрибут ng-app="example"
в <body>
чтобы приложение работало. После этого файл должен выглядеть так:
1
2
3
4
5
6
7
8
|
<!DOCTYPE html>
<html>
<head>
<script src=»https://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.min.js»></script> <script> var module = angular.module(‘example’, []);
</head>
<body ng-app=»example»>
</body>
</html>
|
Директива об атрибутах: 1337 C0NV3R73R
Во-первых, мы создадим простую директиву, которая будет работать аналогично ngBind , но она изменит текст на речь .
Шаг 1: Декларация Директивы
Директивы объявляются с использованием module.directive()
:
1
|
module.directive(‘exampleBindLeet’, function () {
|
Первый аргумент — это имя директивы. Он должен быть в camelCase , но поскольку HTML не чувствителен к регистру, вы будете использовать его в нижнем регистре (example-bind-leet) в вашем HTML-коде.
Функция, переданная в качестве второго аргумента, должна возвращать объект, описывающий директиву. На данный момент у него будет только одно свойство: функция ссылки:
1
2
3
4
|
return {
link: link
};
});
|
Шаг 2: Функция Связи
Вы можете определить функцию перед оператором return или непосредственно в возвращаемом объекте. Он используется для управления DOM элемента, к которому была применена наша директива, и вызывается с тремя аргументами:
1
|
function link($scope, $elem, attrs) {
|
$scope
— это объект области видимости Angular, $elem
— это элемент DOM, которому соответствует эта директива (она заключена в jqLite , подмножество AngularJS наиболее часто используемых функций jQuery), а attrs
— это объект со всеми атрибутами элемента (с нормализованными именами, поэтому example-bind-leet будет доступен как attrs.exampleBindLeet
).
Простейший код для этой функции в нашей директиве будет выглядеть так:
1
2
3
4
5
6
|
var leetText = attrs.exampleBindLeet.replace(/[abegilostz]/gmi, function (letter) {
return leet[letter.toLowerCase()];
});
$elem.text(leetText);
}
|
Сначала мы заменим некоторые буквы в тексте, представленном в атрибуте example-bind-leet
их заменами из таблицы leet. Таблица выглядит так:
1
2
3
4
5
6
|
var leet = {
a: ‘4’, b: ‘8’, e: ‘3’,
g: ‘6’, i: ‘!’, l: ‘1’,
o: ‘0’, s: ‘5’, t: ‘7’,
z: ‘2’
};
|
Вы должны разместить его в верхней части <script>
. Как видите, это самый простой конвертер Leet, поскольку он заменяет только десять символов.
После этого мы преобразовали строку в leet говорит, что мы используем метод text()
jqLite, чтобы поместить его во внутренний текст элемента, которому соответствует эта директива.
Теперь вы можете проверить это, поместив этот HTML-код в <body>
документа:
1
|
<div example-bind-leet=»This text will be converted to leet speak!»></div>
|
Вывод должен выглядеть так:
Но это не совсем то, как ngBind
директива ngBind
. Мы изменим это на следующих шагах.
Шаг 3: Сфера
Прежде всего, вещь, передаваемая в атрибуте example-bind-leet
должна быть ссылкой на переменную в текущей области, а не на текст, который мы хотим преобразовать. Для этого нам нужно создать изолированную область видимости для директивы.
Мы можем достичь этого, добавив объект области видимости к возвращаемому значению нашей директивной функции:
1
2
3
4
5
6
7
8
9
|
module.directive(‘exampleBindLeet’, function () {
…
return {
link: link,
scope: {
}
};
);
|
Каждое свойство в этом объекте будет доступно в области действия директивы. Это значение будет определяться значением здесь. Если мы используем ‘-‘, значение будет равно значению атрибута с тем же именем, что и у свойства. Использование ‘=’ скажет компилятору, что мы ожидаем, что переменная из текущей области будет передана — которая будет работать так же, как ngBind
:
1
2
3
|
scope: {
exampleBindLeet: ‘=’
}
|
Вы также можете использовать что угодно в качестве имени свойства и поставить нормализованное (преобразованное в camelCase) имя атрибута после — или =:
1
2
3
|
scope: {
text: ‘=exampleBindLeet’
}
|
Выберите то, что работает лучше для вас. Теперь нам также нужно изменить функцию ссылки, чтобы использовать $scope
вместо attr
:
1
2
3
4
5
6
7
|
function link($scope, $elem, attrs) {
var leetText = $scope.exampleBindLeet.replace(/[abegilostz]/gmi, function (letter) {
return leet[letter.toLowerCase()];
});
$elem.text(leetText);
}
|
Теперь используйте ngInit или создайте контроллер и измените значение атрибута div
— example-bind-leet
на имя переменной, которую вы использовали:
1
2
3
|
<body ng-app=»example» ng-init=»textToConvert = ‘This text will be converted to leet speak!'»>
<div example-bind-leet=»textToConvert»></div>
</body>
|
Шаг 4: Обнаружение изменений
Но это по-прежнему не так, как работает ngBind
. Чтобы увидеть это, давайте добавим поле ввода, чтобы изменить значение textToConvert после загрузки страницы:
1
|
<input ng-model=»textToConvert»>
|
Теперь, если вы откроете страницу и попытаетесь изменить текст на входе, вы увидите, что в нашем div
ничего не меняется. Это связано с тем, что функция link()
вызывается один раз для каждой директивы во время компиляции, поэтому она не может изменять содержимое элемента каждый раз, когда что-то изменяется в области видимости.
Чтобы изменить это, мы будем использовать метод $ scope. $ Watch () . Он принимает два параметра: первый — это угловое выражение, которое будет оцениваться каждый раз при изменении области действия, второй — функция обратного вызова, которая будет вызываться при изменении значения выражения.
Во-первых, давайте поместим код, который у нас был в функции link()
в локальную функцию внутри нее:
1
2
3
4
5
6
7
8
9
|
function link($scope, $elem, attrs) {
function convertText() {
var leetText = $scope.exampleBindLeet.replace(/[abegilostz]/gmi, function (letter) {
return leet[letter.toLowerCase()];
});
$elem.text(leetText);
}
}
|
Теперь после этой функции мы будем вызывать $scope.$watch()
следующим образом:
1
|
$scope.$watch(‘exampleBindLeet’, convertLeet);
|
Если вы откроете страницу сейчас и что-то измените в поле ввода, вы увидите, что содержимое нашего div
также меняется, как и ожидалось.
Директива об элементах: индикатор выполнения
Теперь мы напишем директиву, которая создаст индикатор прогресса для нас. Для этого мы будем использовать новый элемент: <example-progress>
.
Шаг 1: Стиль
Чтобы наш индикатор выполнения выглядел как индикатор выполнения, нам нужно использовать немного CSS. Поместите этот код в элемент <style>
в <head>
документа:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
example-progress {
display: block;
width: 100%;
position: relative;
border: 1px solid black;
height: 18px;
}
example-progress .progressBar {
position: absolute;
top: 0;
left: 0;
bottom: 0;
background: green;
}
example-progress .progressValue {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
text-align: center;
}
|
Как вы можете видеть, это довольно просто — мы используем комбинацию position: relative
и position: absolute
для позиционирования зеленой полосы и значения внутри нашего элемента <example-progress>
.
Шаг 2: Свойства Директивы
Этот из них потребует немного больше опций, чем предыдущий. Взгляните на этот код (и вставьте его в <script>
):
01
02
03
04
05
06
07
08
09
10
11
|
module.directive(‘exampleProgress’, function () {
return {
restrict: ‘E’,
scope: {
value: ‘=’,
max: ‘=’
},
template: »,
link: link
};
});
|
Как вы можете видеть, мы все еще используем область (с двумя свойствами на этот раз — значение для текущего значения и max для максимального значения) и функцию link (), но есть два новых свойства:
- restrict: ‘E’ — этот указывает компилятору искать элементы вместо атрибутов. Возможные значения:
- «A» — соответствует только именам атрибутов (это поведение по умолчанию, поэтому вам не нужно устанавливать его, если вы хотите сопоставлять только атрибуты)
- ‘E’ — соответствует только именам элементов
- ‘C’ — соответствует только именам классов
- Вы можете объединить их, например, «AEC» будет соответствовать именам атрибутов, элементов и классов.
- template: » — это позволяет нам изменить внутренний HTML-код нашего элемента (есть также templateUrl, если вы хотите загрузить свой HTML-файл из отдельного файла)
Конечно, мы не оставим шаблон пустым. Поместите этот HTML туда:
1
|
<div class=»progressBar»></div><div class=»progressValue»>{{ percentValue }}%</div>
|
Как вы можете видеть, мы также можем использовать выражения Angluar в шаблоне — percentValue
будет взят из области действия директивы.
Шаг 3: Функция Связи
Эта функция будет аналогична предыдущей директиве. Во-первых, создайте локальную функцию, которая будет выполнять логику директивы — в этом случае обновите percentValue
и установите div.progressBar
:
1
2
3
4
5
6
7
|
function link($scope, $elem, attrs) {
function updateProgress() {
var percentValue = Math.round($scope.value / $scope.max * 100);
$scope.percentValue = Math.min(Math.max(percentValue, 0), 100);
$elem.children()[0].style.width = $scope.percentValue + ‘%’;
}
}
|
Как видите, мы не можем использовать .css()
для изменения ширины div.progressBar, потому что jqLite не поддерживает селекторы в .children()
. Нам также нужно использовать Math.min()
и Math.max()
чтобы сохранить значение от 0% до 100% — Math.max()
вернет 0, если precentValue меньше 0, а Math.min()
вернет 100 если percentValue
больше 100.
Теперь вместо двух вызовов $scope.$watch()
(мы должны следить за изменениями в $scope.value
и $scope.max
), давайте использовать $scope.$watchCollection()
, который похож, но работает с коллекциями свойств:
1
|
$scope.$watchCollection(‘[value, max]’, updateProgress);
|
Обратите внимание, что в качестве первого параметра мы передаем строку, которая выглядит как массив, а не как массив JavaScript.
Чтобы увидеть, как это работает, сначала измените ngInit
чтобы инициализировать еще две переменные:
1
|
<body ng-app=»example» ng-init=»textToConvert = ‘This text will be converted to leet speak!’; progressValue = 20; progressMax = 100″>
|
А затем добавьте элемент <example-progress>
ниже div
мы использовали ранее:
1
|
<example-progress value=»progressValue» max=»progressMax»></example-progress>
|
<body>
теперь должно выглядеть так:
1
2
3
4
|
<body ng-app=»example» ng-init=»textToConvert = ‘This text will be converted to leet speak!’; progressValue = 20; progressMax = 100″>
<div example-bind-leet=»textToConvert»></div>
<example-progress value=»progressValue» max=»progressMax»></example-progress>
</body>
|
И вот результат:
Шаг 4: Добавление анимации с использованием jQuery
Если вы добавите входные данные для progressValue
и progressMax
как это:
1
2
|
<input ng-model=»progressValue»>
<input ng-model=»progressMax»>
|
Вы заметите, что при изменении любого из значений изменение ширины происходит немедленно. Чтобы это выглядело немного лучше, давайте используем jQuery для его анимации. Хорошая вещь об использовании jQuery с AngularJS состоит в том, что когда вы включаете jQuery <script>
Angular автоматически заменяет jqLite на него, делая $elem
объектом jQuery.
Итак, давайте начнем с добавления скрипта jQuery в <head>
документа перед AngularJS:
1
|
<script src=»http://code.jquery.com/jquery-2.1.0.min.js»></script>
|
Теперь мы можем изменить нашу updateProgress()
чтобы использовать метод jQuery .animate()
. Измените эту строку:
1
|
$elem.children()[0].style.width = $scope.percentValue + ‘%’;
|
К этому:
1
|
$elem.children(‘.progressBar’).stop(true, true).animate({ width: $scope.percentValue + ‘%’ });
|
И вы должны иметь красиво анимированный индикатор выполнения. Нам пришлось использовать метод .stop (), чтобы остановить и завершить любые ожидающие анимации в случае, если мы изменим любое значение во время анимации (попробуйте удалить его и быстро изменить значения во входных данных, чтобы понять, зачем это нужно).
Конечно, вы должны изменить CSS и, возможно, использовать некоторые другие функции замедления в вашем приложении, чтобы соответствовать вашему стилю.
Вывод
Директивы AngularJS являются мощным инструментом для любого веб-разработчика. Вы можете создать набор своих собственных директив, чтобы упростить и ускорить процесс разработки. То, что вы можете создать, ограничено только вашим воображением, вы можете в значительной степени преобразовать все свои серверные шаблоны в директивы AngularJS.
Полезные ссылки
Вот несколько ссылок на документацию AngularJS: