AngularJS: Міграція з 1.2 на 1.4, 2 ч.

першої частини ми перебрали всі основні проблеми переходу на нову версію, а в цій ми торкнемося того, заради чого ми це робили.
image
Як говорилося раніше, головною причиною переходу може служити істотне збільшення швидкості роботи програми: в 4.3 рази більш швидкі маніпуляції з DOM і в 3.5 рази швидші цикли
$digest
(порівняно з 1.2), як заявили Джеф Крос і Бріан Форд на конференції ngEurope.
Однак ця швидкість здебільшого купується не за рахунок внутрішніх оптимізацій і магії, а за рахунок надання інструментів, які дозволяють писати код більш ефективно.
Давайте розглянемо ці інструменти!
Debug Info
Не новина, що ангуляр витрачає значну частину ресурсів на інформацію, що полегшує дебаг, таку як додавання класів
елементів DOM (наприклад, класів
ng-binding
та
ng-isolated-scope
) або прикріплення до них різних методів для доступу до
scope
(наприклад,
.scope()
та
.isolateScope()
).
Все це корисно і необхідно для роботи таких інструментів, як Protractor і Batarang, однак потрібні ці дані на продакшені?
Починаючи з версії 1.3, debug info можна відключити:
app.config(['$compileProvider', function ($compileProvider) {
$compileProvider.debugInfoEnabled(false);
}]);

Але що робити, якщо нам необхідно продебажить продакшн, а дебаг відключений?
Тут нас врятує метод
.reloadWithDebugInfo()
об'єкта
angular
, а оскільки об'єкт
angular
глобальний, то ми можемо виконати цей код, просто з консолі:
angular.reloadWithDebugInfo();

$applyAsync
З версією 1.3 прийшов сервіс
$applyAsync
, багато в чому схожий за своєю механіки з вже існуючим сервісом
$evalAsync
:
Спрощено кажучи, він додає вираз в чергу, потім чекає (виставляє setTimeout(..., 0), в сучасних браузерах це близько 10 мілісекунд), і якщо за час, що минув, в чергу не додано ще один вислів – запускає
$rootScope.$digest()
.
Це дозволяє запускати безліч паралельних виразів, що впливають на DOM, будучи впевненим у тому, що вони запустяться в один цикл $digest і не турбуючись про занадто частому виклик $apply.
У чому відмінність від $evalAsync?
Головна відмінність в тому, що
$applyAsync
сам виконує всю чергу виразів циклу
$digest
, перед dirty checking, що дозволяє виконати його лише один раз, у той час як черга
$evalAsync
виконується під час брудної перевірки (якщо точніше, то на самому початку циклу брудної перевірки), і будь доданий в чергу вираз (поза $watch) запустить цикл $digest ще раз, що призведе до повторного виконання вираження в цьому ж
$digest
трохи пізніше.
[подробнее]
Проте справжню користь від використання цього інструменту можна побачити у внутрішніх сервісах ангуляра, наприклад,
$httpProvider
.
$http
Головна відмінність сервісу
$http
від інших способів зробити XHR запит – це виклик
$apply
по його завершенню.
Проблема полягає в безлічі паралельних запитів, кожний з яких викликає свого завершення
$apply
, що призводить до гальм.
Проблема вирішена з приходом
$applyAsync
у версії 1.3:
app.config(function ($httpProvider) {
$httpProvider.useApplyAsync(true);
});

Даний код включає використання черзі
$applyAsync
$httpProvider
.
Це дозволяє запускати $apply лише один раз, коли всі промисы одночасних запитів будуть
resolved
, що дає значний приріст продуктивності.
Bind Once
Одна з головних проблем з продуктивністю AngularJS полягає у величезній кількості
$watch
'єрів з-за того, що до всіх виразів застосовується двостороннє зв'язування, але не всі дані цього вимагають.
Для статичних даних досить однобічного скріплення, без навішування вотчера, і раніше це вирішувалося кастомних директивами, але все змінилося в версії 1.3.
Починаючи з версії 1.3, доступний новий синтаксис у вигляді
::
на початку виразу.
Будь-який вираз, що починаються з
::
, буде сприйнято як одностороннє зв'язування і перестане відслідковуватися (unwatch), як тільки у вираженні стануть стабільними і пройде перший цикл
$digest
.
Наприклад:
{{:: foo }}

<button ng-bind=":: foo"></button>

<ul>
<li ng-repeat=":: foo in bar"></li>
</ul>

<custom-directive two-way-bind-property=":: foo"><custom-directive>

Стабільність даних:
Дані вважаються нестабільними до тих пір, поки вони дорівнюють
undefined
. Будь-які інші дані, будь то
NaN
,
false
,
"
,
[]
або
null
, вважаються стабільними і призведуть до
unwatch
виразу.
Це необхідно для статичних даних, які недоступні під час першого циклу $digest.
Наприклад, якщо дані приходять з сервера, то можна просто тримати в змінній значення
undefined
під час очікування запиту. Все це час вотчер цього виразу буде жити і тільки після установки даних, відмінних від
undefined
, віддасть кінці.
Ставитеся до виразів з
::
як до константи: як тільки вона встановлена, її вже неможливо змінити.
Оновити необновляемое:
Припустимо, у нас є вираз, який не оновлюється в 99 випадків з 100 (тобто нам нафіг не впав на неї вотчер), але іноді це потрібно. Як бути? Я задався питанням, чи можна силою оновити bind-once вираз.
Ні, не можна :) Проте, можна написати свою директиву-атрибут, яка змусить зробити re-compile всю директиву у відповідь на якісь події. Приклад доступний тут.


На цьому частина продуктивності закінчується, і можна поговорити про просто приємних булочками:
ngModel Options
Версія 1.3 подарувала на додаток до
ng-model
допоміжну директиву
ng-model-options
, яка відповідає за те, коли модель буде оновлено.
Час оновлення залежить від двох факторів:
1)
updateOn
– спеціальні події (events), за яким відбувається оновлення моделі, наприклад, це може бути
blur
,
click
або якийсь кастомный івент. За замовчуванням завжди стоїть
default
, що означає, що кожен контрол буде використовувати своє власне подія. Якщо ви хочете розширити стандартне подія, додавши своє, не забудьте додати в список
default
, наприклад
{подія: "default customEvent"}
.
2)
debounce
– затримка при оновленні моделі в очікуванні нових даних (за замовчуванням 0, тобто миттєво). Якщо вказати инпуту
{debounce: 300}
і ввести 3 символи з проміжком менше 300 мілісекунд, ваша модель (а значить і різні модифікатори/валідатори) оновиться лише один раз. Крім того,
debounce
можна комбінувати з подіями, вказуючи свою затримку для кожного з них, наприклад:
{подія: "default customEvent", debounce: {default: 0, customEvent: 400}}
.
Це дозволяє нам позбутися від безлічі велосипедів (прощайте, setTimeout/clearTimeout) і істотно підвищує продуктивність (адже ми позбавляємося від марного перезапуску
$digest
, а відповідно і всіх
$watchers
), а також зменшує кількість помилкових спрацьовувань для асинхронної валідації (втім,
$http
сервіс досить розумний, щоб не спамити запитами, а почекати стабільних даних).
Але є ще три корисні опції
Прапор allowInvalid дозволить встановити
$modelValue
, навіть якщо значення є невалидным для нього (за замовчуванням поки значення є невалидным, модель записується
undefined
, що не дозволяє, наприклад, дізнатися проміжне значення)
Прапор setterGetter дозволить встановити в якості
ngModel
свою власну функцію своєрідного посередника між
ngModel.modelValue
та
ngModel.viewValue
виконує роль сетера і геттера. Живий приклад на plunker.
timezone дозволяє встановити часовий пояс для контролів, пов'язаних з часом (
date
або
time
), наприклад
'+0430'
означатиме '4 години 30 хвилин GTM'. За замовчуванням береться часовий пояс браузера.
Ігноруючи updateOn і debounce
Іноді при ручному записі моделі необхідно проігнорувати встановлену в івентах затримку і виконати оновлення миттєво. Для цього існує метод
ngModelCtrl.$commitViewValue()
.
Скасувати зміни
Якщо необхідно скасувати всі зміни і що висять у процесі
debounce
, існує метод
$rollbackViewValue()
(колишній
$cancelUpdate()
), який підганяє вид до актуального стану моделі.
як приклад використання такої можливості офіційна документація пропонує инпат, зміни в якому можна відкотити по натисканні ESC.
Докладна документація
Валідація
Одне з головних поліпшень в плані зручності роботи торкнулося валідації форм.
Раніше доводилося реалізовувати механізм валідації через
ndModel.$formatters
та
ndModel.$parsers
, впливаючи на результат валідації безпосередньо, через
ndModel.$setValidity()
, а реалізація асинхронних перевірок доставляла окрему радість.
Нововведення позначилося на продуктивності:
Адже раніше валидирующие функції запускалися при кожному оновленні в DOM (
$parsers
) або моделі (
$formatters
), часто впливаючи на значення один одного, тим самим перезапуская цикл перевірок заново.
У новій версії валідація запускається, тільки якщо змінено модель і тільки якщо в цій моделі немає помилки (
{parse: true
). Вплив на саму модель або подання контрола всередині валідатора теж виключається, що позитивно впливає на швидкість роботи програми.
Що таке $formatters і $parsers, для чого створені? Їх видалили?Ні, їх не видалили, це інші інструменти для інших речей, і вони як і раніше необхідні.
Та
$formatters
та
$parsers
– це масиви, що містять функциии-обробники, які приймають значення і передають його по ланцюжку наступної функції-обробника. Кожна ланка ланцюжка може модифікувати значення перед тим, як передати його далі.
$formatters
Кожен раз, коли змінюється модель,
$formatters
перебирає обробники в масиві в зворотному порядку, від кінця до початку. Останнім передане значення відповідає за те, як буде представлена модель в DOM. Іншими словами,
$formatters
відповідає за те, як буде конвертований
ngModelCtrl.$modelValue
на
ngModelCtrl.$viewValue
.
$parsers
Кожен раз, коли контрол читає значення DOM,
$parsers
перебирає масив обробників в нормальному порядку, від початку до кінця, передаючи значення по ланцюжку. Останнім передане значення відповідає за те, як буде представлено значення в моделі. Іншими словами,
$parsers
відповідає за те, як буде конвертований
ngModelCtrl.$viewValue
на
ngModelCtrl.$modelValue
.
Де?
Перш за все, їх використовує в своїй роботі сам ангуляр. Наприклад, якщо ви створите контрол з валідацією на мінімальну довжину, а потім перевірите масив
$formatters
, то помітите, що той не порожній, а вже містить одну функцію-обраточик, конвертирующую значення DOM в рядок.
Приклад вище – це використання обробників для попередньої обробки (sanitize) значення, якщо ми хочемо бути впевнені, що воно потрапить в модель тільки в строго заданому вигляді.
Інші два популярних способу використання це двостороння фільтрація значень (наприклад, коли користувач вводить в инпут "10, 000", а в моделі зберігається "10000" і навпаки) і створення масок (наприклад, коли користувач повинен заповнити маску телефону "+7 (000) 000-00-00", а в моделі ми будемо зберігати "70000000000").
Як видно з прикладів – інструмент незамінний.
Як працювала валідація раніше?Вичерпну інформацію про старих методах валідації можна отримати за даними статей на Хабре:

Чому не варто використовувати старі методи валідації і далі?Справа в тому, що повернення
undefined
ndModel.$parsers
тепер призводить до виставлення
ngModelCtrl.$modelValue
на
undefined
і додаванню в
ndModel.$errors
значення
{parse: false}
, тобто инвалидации поля.
В цьому випадку валідатори (
$validators
та
$asyncValidators
) навіть не починають своєї роботи.
Це поведінка можна відключити в
ngModelOptions
, виставивши прапор
allowInvalid
на
true
.
Починаючи з версії 1.3 у нас з'явилися інструменти для зручної синхронної і асинхронної перевірки.
Також варто згадати, що саме робота нових валідаторів зав'язана на оновлення моделі, то вона залежить і від
ngModelOptions
, про яких говорилося вище.
Синхронна
Для синхронної валідації нова версія пропонує нам колекцію
ndModel.$validators
, розширюючи її функцією-валідатором.
Функція-валідатор повинна повертати
true
або
false
, для валідних і валідних значень відповідно.
Приклад:
ngModel.$validators.integer = function(modelValue, viewValue) {
// Визначаємо, що порожня модель є валидной 
if (ctrl.$isEmpty(modelValue)) {
return true;
}

if (INTEGER_REGEXP.test(viewValue)) {
return true; // Поле валидно
}

return false; // Поле не валидно
};

Асинхронна
Для асинхронної валідації використовується колекція
ngModelCtrl.$asyncValidators
, з тією ж логікою розширюючи її функцією-валідатором.
Основні відмінності роботи асинхронної версії:
  • Асинхронна валідація запускається тільки якщо всі синхронні валідації виявилися валідними
  • Функція-валідатор повинна повертати тільки промис, здійснюючи його
    resolve()
    або
    reject()
    , для валідних і валідних значень відповідно.
Промисы в AngularJS породжуються спеціальним сервісом
$q
, а також сервісами на зразок
$timeout
та
$http
, які в своїй основі також використовують
$q
.
У колекції зберігається не більше одного промиса возращенного одним валідатором. Це означає, що якщо викликати валідацію кілька разів, то буде враховуватися тільки промис з останнього дзвінка валідатора, незалежно від результату роботи попередніх промисов.
З моменту передачі функцією-валідатором промиса, і до його дозволу (
resolve()
або
reject()
) поле
ngModelCtrl.$pending
зберігає ім'я валідатора, а
ngModelCtrl.$valid
та
ngModelCtrl.$invalid
дорівнюють
undefined

Будьте обережні з цією особливістю: до тих пір поки йде валідація форма буде не валидной, при цьому ви не побачите ніяких помилок у
FormCtrl.$errors
, але можете побачити "очікують" валідатори
FormCtrl.$pending
.
Приклади:
ngModelCtrl.$asyncValidators.username = function(modelValue, viewValue) {
// Визначаємо, що порожня модель є валидной 
if (ctrl.$isEmpty(modelValue)) {
return $q.when();
}

var def = $q.defer();

// Емуляціях асинхронний запит
$timeout(function() {
if (usernames.indexOf(modelValue) === -1) {
def.resolve(); // Ім'я доступно, поле валидно
} else {
def.reject(); // Ім'я зайнято, поле не валидно
}
}, 2000);

return def.promise;
};

ngModelCtrl.$asyncValidators.uniqueUsername = function(modelValue, viewValue) {
var value = modelValue || viewValue;

// Перевіряємо, чи є ім'я користувача
return $http.get('/api/users/' + value).
then(function resolved() {
// Користувач знайдений, означає ім'я зайнято, а поле не валидно
return $q.reject('exists');
}, function rejected() {
// Користувач не знайдений, ім'я доступно, а поле валидно
return true;
});
};

Варто акуратно використовувати асинхронну валідацію з полями, які використовують модифікатори значення (через
$formatters
та
$parsers
): це може викликати множинні спрацювання, а також неправдиву валідацію або инвалидацию поля.
Приклад валідації для таких випадків
var pendingPromise;

ngModelCtrl.$asyncValidators.checkPhoneUnique = function (modelValue) {
if (pendingPromise) {
return pendingPromise;
}

var deferred = $q.defer();

if (modelValue) {
pendingPromise = deferred.promise;

$http.post('/запит', {value: modelValue})
.success(function (response) {
if (response.Result === 'Хитрий умова з сервера') {
deferred.resolve();
} else {
deferred.reject();
}
}).error(function () {
deferred.reject();
}).finally(function () {
pendingPromise = null;
});
} else {
deferred.resolve();
}

return deferred.promise;
};

ngMessages
Модуль
ngMessages
покликаний полегшити показ повідомлень на сторінці.
Не дивлячись на те, що цей модуль можна зручно використовувати для будь-яких повідомлень на сторінці, зазвичай його використовують для показу помилок у формах. Щоб зрозуміти, які проблеми вирішує цей інструмент, давайте подивимося, яку біль доставляв старий спосіб відображення помилок, і порівняємо його з новим.
Для прикладу порівняння будемо використовувати дану форму додавання коментаря:
<form name="commentForm">
<ul class="warnings"
ng-if="commentForm.$error && commentForm.$dirty">
...
</ul>

<label>Ім'я:</label>
<input type="text" 
name="username"
ng-model="comment.username" required>

<label>Коментар:</label>
<textarea name="message"
ng-model="comment.message" 
minlength="5" maxlength="500"></textarea>
</form>

Залишимо за бортом реалізацію безпосередньо валідації і розглянемо тільки відображення повідомлень про необхідні умови:
  • Поле "Ім'я" не повинно бути порожнім
  • Поле "Повідомлення" не повинно бути менше 5 символів
  • Поле "Повідомлення" не повинно бути більше 500 символів
    Помилки будемо відображати в блоці
    .errors
    .
Старі методи показу помилок
Приклад вийшов довгий, тому я приховав його під спойлер.Тепер уявімо, як нам відобразити помилки для цих полів:
<form name="commentForm">
<ul class="warnings"
ng-if="commentForm.$error && commentForm.$dirty">
<span ng-if="commentForm.message.$error.minlength">
Повідомлення не може бути менше 5 символів
</span>
<span ng-if="commentForm.username.$error.maxlength">
Повідомлення не може бути більше 500 символів
</span>
<span ng-if="commentForm.username.$error.required">
Це обов'язкове поле
</span>
</ul>

<label>Ім'я:</label>
...

<label>Коментар:</label>
...
</form>

Що ж, поки що все виглядає не так вже і погано, так?
Але чому повідомлення про нікнеймі і коментарі показуються всі разом? Давайте виправимо це, додавши послідовне відображення:
<form name="commentForm">
<ul class="warnings"
ng-if="commentForm.$error && commentForm.$dirty">
<span ng-if="commentForm.message.$error.minlength &&
commentForm.username.$valid">
Повідомлення не може бути менше 5 символів
</span>
...
</ul>

<label>Ім'я:</label>
...

<label>Коментар:</label>
...
</form>

Ви все ще думаєте: «це не так погано»? Уявіть, що полів на сторінці не 2, а 20, і у кожного мінімум 5 повідомлень. У подібному стилі наша сторінка швидко перетворитися на смітник з умов.
Звичайно, є кращі практики для реалізації даної задачі, спеціальні директиви і милиці, розширюють поведінка
FormController
(наприклад, в нашому проекті все контроли розширювалися властивістю
showError
, яке зберігало поточну помилку), але всі вони програють в зручності методів, про які ми будемо говорити далі.
Нові методи показу помилок
З'єднання
ngMesssages
поставляється окремим модулем, і перед тим як почати працювати з ним, нам треба його підключити. Скачайте або встановіть модуль через пакетний менеджер і підключіть до проекту:
<script src="path/to/angular-messages.js"></script>

І додамо в залежності:
angular.module('myApp', ['ngMessages']);

Базова робота з даним модулем зводиться до роботи з двома директивами:
  1. ng-messages
    – контейнер, що містить наші повідомлення
  2. ng-message
    – безпосередньо повідомлення
ng-messages
приймає в якості аргументу колекцію, по ключам якої будуть звірятися і показуватися вже
ng-message
, які приймають в якості аргументу для порівняння рядок або вираз (починаючи з 1.4).
Давайте повторимо приклад тієї ж форми додавання коментаря, але вже за допомогою
ngMessages
:
<div ng-messages="commentForm.message.$error" class="warnings">
<p ng-message="minlength">
Повідомлення не може бути менше 5 символів
</p> 
<p ng-message="maxlength">
Повідомлення не може бути більше 500 символів
</p>
<p ng-message="required">
Це обов'язкове поле
</p>
</div>

ngMessages
можна використовувати і в якості елемента:
<ng-messages for="commentForm.message.$error" class="warnings">
<ng-message when="minlength">
Повідомлення не може бути менше 5 символів
</ng> 
<ng-message when="maxlength">
Повідомлення не може бути більше 500 символів
</ng>
<ng-message when="required">
Це обов'язкове поле
</ng>
</ng-messages>

І так, сходу
ngMessages
вирішили для нас дві проблеми:
  • Локшина з умов перетворилася в зрозумілій
    switch
    подібний список
  • Проблема з виведенням повідомлень по одному вирішилася сама собою
Крім того, вирішується і проблема пріоритезації виведення повідомлень. Тут все просто: повідомлення показуються відповідно їх розташуванню в DOM.
Висновок множинних повідомлень можна включити додаванням атрибута
ng-messages-multiple
до директиви
ng-messages
:
<ng-messages ng-messages-for multiple="commentForm.message.$error">
...
</ng-messages>

Повторне використання повідомлень – це ще одна важлива особливість
ngMessages
, яка дозволяє підключати наші повідомлення, подібно шаблонами, в будь-якому необхідному місці:
<b class=«spoiler_title>Увага, далі піде мова про версії 1.4, у версії 1.3 є відмінністьВерсія 1.3 використовує
ng-messages-include
в якості додаткового атрибута
ng-messages
, в той час як у більш пізніх версіях
ng-messages-include
це самодостатня дочірня директива поряд з
ng-message
:
1.3
<ng-messages ng-messages-include="length-message" 
for="commentForm.message.$error">
</ng-messages>

1.4+
<ng-messages for="commentForm.message.$error">
<ng-messages-include="length-message"></ng-messages-include>
</ng-messages>

Це дозволяє нам винести часто повторювані повідомлення (наприклад, повідомлення про довжині поля) в окремий шаблон, який ми будемо включати в потрібних місцях без необхідності копипасты:
<script type="script/ng-template" id="length-message">
<ng-message when="minlength">
Значення поля дуже короткий
</ng-message>
</script>

...

<ng-messages for="commentForm.message.$error">
<ng-messages-include="length-message"></ng-messages-include>
</ng-messages>

<ng-messages for="anotherForm.someField.$error">
<ng-messages-include="length-message"></ng-messages-include>
</ng-messages>

Приклад з довжиною може здаватися натягнутим, але хей, таких повідомлень може бути десятки і навіть сотні. В такому випадку хорошою практикою в даному випадку було б винесення всіх використаних на проекті повідомлень в одне місце для подальшого їх повторного використання.
Ми не обмежені статичним викликом шаблонів, і з версії 1.4 нам доступні динамічні повідомлення, які можливо створювати через директиву
ng-message-exp
:
// error = {type: required, message: 'Поле не повинно бути порожнім'};
<ng-messages for="commentForm.message.$error">
<ng-message-exp="error.type">
{{ error.message }}
</ng-message-exp>
</ng-messages>

ng-message-exp
на відміну від
ng-message
, приймає в якості аргументу вираз (
expression
). Це дозволяє нам, наприклад, показувати повідомлення, які генерує нам сервер в AJAX запиті.
Динамічний висновок повідомлень – це найпотужніший інструмент. Адже він дозволяє нам генерувати будь-які типи помилок з будь-яким змістом тексту без прив'язки до статичного поданням або прив'язаної до
ng-messages
колекції!
Що ми маємо в результаті:
  • Сформований список без локшини з умов
  • Приоритезированный висновок повідомлень по одному прямо з коробки
  • Повторне використання повідомлень на проекті
  • Зручний інструмент для створення динамічних повідомлень
  • Термінову необхідність кидати все і переносити свій старий механізм показу помилок на
    ng-messages
Контролери
bindToController
[приклад]
Кожен, хто любить використовувати
controller as
синтаксис, знає біль використання його для директив з ізольованим
scope
.
Проблема полягає у двосторонньому зв'язування властивості, зазначеного через
this.something
всередині контролера. Будь-які зміни значення ззовні ні до чого не приведуть.
Наприклад:
app.directive('someDirective', function () {
return {
scope: {
name: '='
},
controller: function () {
this.name = 'Foo'
},
controllerAs: 'ctrl'
...
};
});

Будь-які зміни
name
з контролерів вище ні до чого не приведуть.
Це можна вирішити через жопу вотчер:
$scope.$watch('name', function (newValue) {
this.name = newValue;
}.bind(this));

Але це незручно, костыльно, і що робити, якщо в скоупе не одну властивість, а п'ятдесят?
Рішення прийшло з версією 1.3:
Зустрічайте властивість
bindToController
.
app.directive('someDirective', function () {
return {
scope: {
name: '='
},
controller: function () {
this.name = 'Foo'
},
bindToController: true,
...
};
});

Тепер
ctrl.name
пов'язано з
$scope.name
і буде змінюватися разом з ним.
З версією 1.4 був доданий ще більш зручний синтаксис:
app.directive('someDirective', function () {
return {
scope: true,
bindToController: {
name: '='
},
controller: function () {
this.name = 'Foo'
},
...
};
});

Тепер можна передавати об'єкт визначення
scope
прямо в
bindToController
.
Все, що передано в
bindToController
, буде прив'язане до контролера, а все, що передано в
scope
, буде прив'язане до
scope
відповідно.
При цьому вказувати щось для
scope
зовсім не обов'язково, вистачить
true
. В цьому випадку все зазначене в
bindToController
причепиться і для
scope
.
Фільтри
{{ expression | filter }}

Динамічні фільтри
Тут мова піде про фільтри, переважно зав'язаних на даних, що надходять ззовні і не залежать від виразу (
expression
), до якого прив'язаний фільтр (
filter
). Це може бути, наприклад, фільтр, який використовує усередині себе якийсь сервіс.
до версії 1.3 фільтри були досить дурними: навішуючи на вираз вотчер, вони постійно заново обчислювали результат цього фільтра. Це одна з причин, чому не варто використовувати безліч фільтрів в одному місці, навантажуючи зайвою роботою цикл
$digest
, і одна з причин, чому нові фільтри працюють швидше. У новій версії фільтри ведуть себе набагато розумнішими і не обчислюють значення фільтра до тих пір, поки не зміниться вираз, до якого прив'язаний даний фільтр.
Живий приклад на plunker
Однак виникає проблема: як оновити значення фільтра, якщо воно залежить не лише від виразу, а від якихось зовнішніх чинників? Іншими словами, як повернути фільтру своє старе, «дурну» поведінку, коли нам це потрібно?
Для цього 1.3 були введені поняття статичного (
stateless
) і динамічного (
stateful
) фільтрів. За замовчуванням фільтр веде себе як
stateless
. Змінити його поведінку можна, виставивши нашого фільтру прапор
$stateful
на
true
.
Приклад:
angular.module('myApp', [])
.filter('customFilter', ['someService', function (someService) {
function customFilter(input) {
// маніпуляція даними стороннім сервісом someService
input += someService.getData();
return input;
}

customFilter.$stateful = true;

return customFilter;
}]);

Breaking change:
Уважний читач міг помітити, що така зміна, по суті, може зламати поведінка старих динамічних фільтрів. На жаль, я забув описати цю особливість в першій частині, але таку можливість слід враховувати.
Фільтр
dateFilter

В даний фільтр тепер додана підтримка тижнів як формату
weeks

Висновок
На цьому все. Якщо я забув якісь важливі особливості нових версій чи знаєте ресурси з більш повним описом їх, поправляйте мене в коментарях.
Від себе додам блог, з достатньо повним описом всіх основних фіч нових версій ангуляра.
Спасибі хабрахабруЗа те, що нарешті зробили markdown. Спочатку стаття була написана саме в цій розмітці і валявся майже пів року з-за моєї ліні і не бажання переводити все в html.
А це котик, для тих, хто дочитав до кінця :)
image

Яку версію використовуєте ви?

/>
/>


<input type=»radio" id=«vv72745»
class=«radio js-field-data»
name=«variant[]»
value=«72745» />
Все ще висимо на 1.2 і нижче
<input type=«radio» id=«vv72747»
class=«radio js-field-data»
name=«variant[]»
value=«72747» />
Перейшли на 1.3
<input type=«radio» id=«vv72749»
class=«radio js-field-data»
name=«variant[]»
value=«72749» />
Оновилися до 1.4
<input type=«radio» id=«vv72751»
class=«radio js-field-data»
name=«variant[]»
value=«72751» />
Ми давно на 1.5
<input type=«radio» id=«vv72753»
class=«radio js-field-data»
name=«variant[]»
value=«72753» />
Оновилися відразу до 2.0+
<input type=«radio» id=«vv72755»
class=«radio js-field-data»
name=«variant[]»
value=«72755» />
Hail ReactJS!

Проголосувало 35 осіб. Утрималося-10 чоловік.


Тільки зареєстровані користувачі можуть брати участь в опитуванні. Увійдіть, будь ласка.


Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.