Статьи

Некоторые ошибки AngularJS

В последние несколько месяцев я активно использую AngularJS. Это изумительная платформа для современных веб-приложений. Но, как и в случае с каждой новой игрушкой, есть некоторые острые углы, о которых вы должны знать. В этом посте я расскажу о некоторых ошибках, с которыми я столкнулся. Предполагая, что вы уже знакомы с Angular, это может быть полезно.

Мерцающий интерфейс

Функция автоматической привязки данных в Angular потрясающая. Однако у него есть обратная сторона: перед инициализацией Angular ваша страница может показывать пользователю непарсированные выражения. Когда DOM готов и Angular вычисляет и заменяет значения, это приводит к ужасному мерцающему эффекту:

Неанализированное угловое выражение

Вышеуказанная ситуация является прямым результатом рендеринга этого примера из урока Angular :

<body ng-controller="PhoneListCtrl">
  <ul>
    <li ng-repeat="phone in phones">
      {{ phone.name }}
      <p>{{ phone.snippet }}</p>
    </li>
  </ul>
</body>

Если у вас есть одностраничное приложение, эта проблема возникает только при первой загрузке страницы. К счастью, это легко предотвратить: не используйте встроенные {{ }}выражения. Вместо этого используйте директиву ng-bind:

<body ng-controller="PhoneListCtrl">
  <ul>
    <li ng-repeat="phone in phones">
      <span ng-bind="phone.name"></span>
      <p ng-bind="phone.snippet">Optional: visually pleasing placeholder</p>
    </li>
  </ul>
</body>

Вам нужен тег, чтобы применить эту директиву, поэтому я добавил <span>имя телефона. Что происходит, так это то, что изначально отображается значение внутри тега (но вы можете оставить его пустым). Затем Angular инициализирует и заменяет внутренности связанного тега результатом выражения. Обратите внимание, что вам не нужно добавлять фигурные скобки внутри свойства ng-bind . Гораздо чище! Если вам нужно составное выражение, используйте вместо этого ng-bind-template . В этом варианте вам нужны фигурные скобки, чтобы различать строковые литералы и выражения.

Другой вариант — полностью скрыть элементы или даже все ваше приложение, пока Angular не будет готов. Angular обеспечивает нг-плащ для этой цели. Он работает, внедряя правило CSS в самом начале фазы инициализации на страницу. Кроме того, вы можете включить это правило скрытия CSS в свою собственную таблицу стилей, чтобы она загружалась раньше. Затем Angular удалит стиль плаща, когда он будет готов к работе, представив ваше приложение (или назначенные элементы) сразу и полностью отрисованное.

JQuery и Angular

Угловой не зависит от JQuery. Фактически, источник Angular содержит встроенную облегченную альтернативу: jqLite . Тем не менее, когда Angular обнаруживает наличие версии jQuery на вашей странице, он использует эту полную реализацию jQuery вместо jqLite. Один прямой путь, которым это проявляется, — это абстракция элемента Angular. Например, в директиве вы получаете доступ к элементу, к которому применяется директива:

angular.module('jqdependency', [])
  .directive('failswithoutjquery', function() {
    return {
      restrict : 'A',
      link : function(scope, element, attrs) {
               element.hide(4000)
             }
    }
});

(Смотрите этот пример в действии в этом plunkr )

Но это elementэлемент jqLite или jQuery? Это зависит. В руководстве говорится:

Все ссылки на элементы в Angular всегда заключаются в jQuery или jqLite; они никогда не являются необработанными ссылками DOM.

Так что это будет элемент jqLite, только если Angular не обнаружил jQuery. hide()Метод существует только для элементов JQuery, то есть этот пример работает только тогда , когда Угловые использует JQuery. Если вы (случайно) изменили порядок тегов скриптов AngularJS и jQuery на своей странице, этот код нарушается! Перестановка тегов сценария может случаться не очень часто, но меня это поразило, когда я начал модульную базу кода. Особенно, когда вы начинаете использовать загрузчик модулей (например, RequireJS ), это может привести к появлению уродливой головы. Для RequireJS я решил это, явно заявив, что Angular на самом деле зависит от JQuery в конфигурации shim .

Другим выводом может быть то, что вы не должны вызывать специфичные для jQuery методы через оболочку элементов Angular. Вместо этого вы можете использовать, $(element).hide(4000)чтобы совершенно ясно понять, что вы на самом деле зависите от jQuery. В этом случае изменение порядка тегов скрипта не нарушит директиву.

минификация

Особое внимание следует уделять угловым приложениям, которые необходимо минимизировать. В противном случае сообщения об ошибках, как 'Unknown provider: aProvider <- a'будет преследовать вас. Как и во многих других вещах, эта информация скрыта где-то в официальной документации . Короче говоря, Angular зависит от имен параметров функции для выполнения внедрения зависимости. Минификатор, который, к счастью, не знает о специальной семантике имен формальных параметров в Angular, отображает их в кратчайший возможный идентификатор. Решение? Используйте мини-дружественный метод метода инъекции. Из этого:

module.service('myservice', function($http, $q) { 
   // This breaks when minified
});

К этому:

module.service('myservice', [ '$http', '$q', function($http, $q) { 
   // Using the array syntax to declare dependencies works with minification!
}]);

Синтаксис массива принимается везде, где принимается функция, которая должна быть введена. Мой совет — использовать этот синтаксис с самого начала. Это предотвратит много боли в будущем, если вы решите минимизировать свой JavaScript. Там также, кажется, есть автоматическое переписывание , хотя я не знаю, насколько хорошо это работает.

Последний совет: если вам нужно переписать свои функции с использованием синтаксиса массива, применяйте его в каждом месте, где Angular вводит зависимости. Это включает в себя директивы, но также контроллеры на директивы. Место, которое легко упустить (если судить по опыту):

// the directive itself needs array injection syntax:
module.directive('directive-with-controller', ['myservice', function(myservice) {
    return {
      controller: ['$timeout', function($timeout) {
        //  but this controller needs array injection syntax, too!  
      }],
      link : function(scope, element, attrs, ctrl) {
                
      }
    }
}]);

Обратите внимание, что функция link не нуждается в синтаксисе массива, поскольку на самом деле она не является целью внедрения. Это функция с фиксированной арностью, вызываемая непосредственно Angular. Впрочем, инъекции с уровня директивы можно использовать внутри функции ссылки.

Директивы никогда не делаются

Одна раздражающая проблема с директивами заключается в том, что вы не понимаете, когда директива «сделана». При интеграции плагина jQuery в директиву такой хук будет очень полезен. Допустим, вы хотите отобразить таблицу с динамическими (ng-repeat) данными в виде таблицы данных jQuery. Вам нужно позвонить $('.mytable').dataTable()в какой-то момент, когда все данные на самом деле материализованы в HTML-таблице (и желательно до того, как они будут вставлены в DOM), но вы не можете.

Почему это? Привязка данных Angular обеспечивается непрерывным циклом дайджеста, который может привести к обновлениям и повторному отображению директив. Как таковой, никогда не бывает момента, когда фреймворк находится в состоянии покоя при применении директив. Один (по общему признанию, некрасивый) обходной путь — запланировать вызов jQuery dataTable вне текущего цикла дайджеста, используя timeout:

angular.module('table',[]).directive('mytable', ['$timeout', function($timeout) {
    return {
      restrict : 'E',
      template: '<table class="mytable">' +
                   '<thead><tr><th>counting</th></tr></thead>' +
                   '<tr ng-repeat="data in datas"><td></td></tr>' +
                '</table>',
      link : function(scope, element, attrs, ctrl) {
         scope.datas = ["one", "two", "three"]
         // Doesn't work, shows an empty table:
         // $('.mytable', element).dataTable()   
         // But this does:
         $timeout(function() { 
           $('.mytable', element).dataTable(); 
         }, 0)
      }
    }
}]);

(Смотрите этот пример в действии в этом plunkr .)

В нашей кодовой базе мы даже столкнулись с ситуацией, когда $timeoutбыло необходимо дважды вложенное . Еще один сумасшедший обходной путь — добавить <script>в шаблон тег, который выполняет обратный вызов для функции Angular scope.$apply(). Достаточно сказать, что это не идеально. Из-за природы Angular это вряд ли изменится.

Несмотря на это, Angular по-прежнему является моей любимой клиентской средой JS. Вы сталкивались с другими подводными камнями при использовании Angular? Или вы решили похожие проблемы по-другому? Оставить комментарий!