AngularJS: як я відмовився від ng-include і пов'язав стану двох контролерів

    У минулій статті я розповідав про своє перше знайомство з AngularJS. З тих пір пройшов вже рік, зараз у мене нові проекти та інші, часто менш тривіальні завдання. Тут я опишу кілька нюансів, з якими мені довелося зіткнутися в процесі роботи над однією з систем. Сподіваюся, читачі зможуть отримати користь з моїх практик.
 
 

Пошук і якір

Припустимо, що нам надійшла завдання розробити клієнтську частину для нашого нового проекту. Це каталог, в якому зберігатимуться сотні тисяч документів. Оскільки він досить великий, в API передбачена можливість завантажувати елементи посторінково (із зазначенням початкового індексу) а також фільтрувати по окремих полях в документі.
А для того, щоб користувачі не губилися в системі і могли ділитися між собою інформацією, клієнт повинен зберігати свій стан в адресному рядку.
 
Що ж, завдання зрозуміло. Приступаємо.
 
 
Щоб зберігати значення фільтрів, створимо сервіс $ query . У нього буде два методи:
 
 
     
  • push (query) — отримує об'єкт з набором фільтрів і додає їх в адресний рядок (поле search).
  •  
  • parse () — перетворить search назад в набір фільтрів.
  •  
 
Тут слід зробити відступ. Оскільки на сторінці використовується кілька шаблонів (наприклад, для пагінацію), в адресний рядок автоматично додається решітка (#). Це відбувається через те, що ng-include використовує сервіс <a href="https://docs.angularjs.org/api/ng/service/$location"> $ location
, за наявності якого angular починає вважати, що ми робимо односторінкове додаток.
Відповідно, об'єкт виду
 
 
{
  index: 0,
  size: 20
}

перетвориться на
 
http://localhost:1337/catalog#?index=0&size=20
Але постійте. Користувачі хочуть не тільки отримувати стан сторінки, а й відзначати на ній окремий документ.
Офіційна документація в такому випадку радить використовувати <a href="https://docs.angularjs.org/api/ng/service/$anchorScroll"> $ anchorScroll або scrollTo .
 
Тобто тепер ми отримаємо наступне:
 
http://localhost:1337/catalog#?index=0&size=20&scrollTo=5
У цей момент моє естетичне почуття заволала до пошуку іншого рішення.
 
Першою думкою було відмовитися від ng-include , щоб адресний рядок більше не піддавалася насильству з боку ангуляр. Але що тоді робити з шаблонами? Вихід був тільки один: написати власну директиву для роботи з шаблонами.
 
 

З блекджек і шаблонами

З директивою проблем не виникло. Для роботи з шаблонами angular використовує сервіс <a href="https://docs.angularjs.org/api/ng/service/$templateCache"> $ templateCache . У нього можна покласти шматок html-коду за допомогою text / ng-template або методу put () . Також, за аналогією з ng-include , передбачимо виконання коду з атрубіта onload .
 
Код директиви:
 
 
app.directive('template', ['$templateCache', '$compile', function ($templateCache, $compile) {
    return {
        scope: false,
        link: function (scope, element, attrs) {
            var tpl = $compile($templateCache.get(attrs.orgnTemplate))(scope);

            tpl.scope().$eval(attrs.onload || '');
            element.after(tpl);
            element.remove();
        }
    }
}]);

 
Тепер ми зможемо використовувати шаблони наступним чином:
 
 
<div data-template="paging" data-onload="foo = 'bar'">

Вирішивши проблему з $ location , я трохи переписав сервіс $ query , щоб тепер він працював виключно з history API.
 
До слова, не намагайтеся використовувати їх разом. Це призведе до нескінченного циклу.
Так що тепер адресний рядок вийшла більш зрозумілою і приємною на вигляд:
 
http://localhost:1337/catalog?index=0&size=20#5
І переміщення по якорям більше не вимагає додаткового коду.
 
 

Легкість спілкування

Розбивши сторінку на окремі шаблони і контролери, я несподівано зіткнувся з іншою проблемою: контролери повинні взаємодіяти між собою. Навіть якщо не складаються в батьківських відносинах. А в окремих випадках (знову ж, пагінація), контролери повинні синхронізувати свій стан.
 
Перший варіант полягав у взаємодії між контролерами через події. У такому випадку на кожну дію контролери розсилають один-одному події. На жаль, в моєму випадку кількість і різноманітність подій на квадратний сантиметр коду стало переходити все розумні рамки. Я вирішив відмовитися від оптимізації і зробити окремий механізм для обміну інформацією незалежно від поточного scope .
 
Так з'явився сервіс $ store . У першому варіанті у нього був один метод:
 
 
     
  • value (key, value) — зберігає або витягує значення по ключу.
  •  
У контролери був доданий наступний код:
 
 
$scope.$watch(function () {
  return $store.value('foo');
}, function (data) {
  doSomething(data);
}, true);

Тепер, коли мені було потрібно синхронізувати стан двох і більше контролерів, я лише Перезаписувати значення в ключі:
 
 
$store.value('stream', data);

 
Не забувайте, що всі сервіси є Сінглтон, тому при додаванні сервісу одночасно в декілька контролерів, ми отримуємо доступ до одного і того ж об'єкту.
Пізніше, коли я трохи автоматизував передачу даних між двома шаблонами (наприклад, список елементів тепер автоматично прив'язувався до пагінацію за допомогою свого $ id ), в сервіс був доданий метод alias () :
 
 
     
  • alias (key, values ​​...) — додає або повертає список синонімів для зазначеного ключа.
  •  
Таким чином, у мене з'явилася можливість вказувати alias в атрибуті onload директиви шаблону. Грубо кажучи, якщо в контролері раптом з'являється потреба запросити стан, це можна зробити не за оригінальним ключу, який може бути недоступний, а за заздалегідь заданому значенню.
 
 

Замість післямови

Так вийшло, що, здавалося б, тривіальна задача переросла в повноцінний рефакторинг. Втім, по його закінченні, принаймні, на мій погляд, код став набагато простіше для читання і більш передбачуваний в роботі. Я більше не гублюся в нескінченних події, харчуюся тільки здоровою їжею і займаюся спортом. Сподіваюся, ця стаття допоможе іншим знайти душевний спокій і навчитися чомусь новому. Успіху і приємного дня!
 
    
Джерело: Хабрахабр

0 коментарів

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