январь 1, 2015 · angularjs

Email приложение на AngularJS

Создадим простой email-клиент и изучим ключевые аспекты AngularJs.
В конце этого руководства приложение сможет отображать список писем, искать по теме, открывать и удалять письма.

Требования:

Темы изучения:

Замечание: Это руководство находится в свободном доступе. Исходный код вы можете найти на GitHub.

Клиентский MVC

Начнем с ключевой особенности AngularJS: клиентский MVC.

MVC состоит из модели, отображения и контроллера. Рассмотрим их по отдельности:

Паттерн MVC — подход для организации приложения, зарекомендовший себя за многие годы.

Забавный факт: Ruby on Rails — это MVC фреймворк для языка Ruby.

Angular использует MV* подход, где * – это любой из возможных вариантов (другое название MVW: Model, View, Whatever).
Другими словами, использование контроллера несколько отличается от обычного подхода. Но сейчас это не относится к делу, давайте сделаем что-то работающее.

Установка AngularJS

Добавим Angular на вашу страницу

Angular содержится в одном js файле, который нужно подключить в конце страницы.
Я бы советовал расположить его именно внизу, а не в head, для лучшего рендеринга страницы.

Заметка: Вам нужно добавить библиотеку Angular, прежде чем мы начнем использовать все её преимущества. Так что сделайте это.

Для этого приложения мы будем использовать версию 1.2.22, которая на данный момент последняя.
Сейчас эта версия рекомендована, т.к сторонние плагины не тестировались на последних бета версиях.

Для загрузки AngularJS перейдите на angularjs.org, нажмите на кнопку “Download”.
Выберите ветку “legacy”, которая содержит только стабильные билды, и нажмите “Download”.

Ваш HTML должен выглядеть вот так:

<html>
    <head>
    </head>
    <body>
        <div></div>
        <script src="lib/jquery-v1.11.1.js"></script>
        <script src="lib/angular-v1.2.22.js"></script>
    </body>
</html>

Заметка: также мы подключим библиотеку jQuery.

В Angular есть встроенный инструмент под названием jQLite.
Это jQuery-подобная микро-библиотека, которая поставляется вместе с Angular.
jQLite слишком “легкая” и не имеет многих отличных вещей, реализованных в jQuery.
Так же хорошо иметь возможность использовать множество плагинов, написаных специально для jQuery.

Полезно знать: Если подключена полноценная jQuery, то Angular определяет это и использует вместо jQLite.

** Исходники примера: 01-include-angular **

Настройка областей видимости и директив

Настроим ваше приложение

Когда вы изучаете новый фреймворк, очень важно иметь что-то рабочее с самого начала. Это позволяет вам эксперементировать.
Но, прежде чем мы начнем, нужно обсудить области видимости в Angular и наши первые 2 директивы: ng-app и ng-controller.

Одна из фундаментальных основ Angular – это области видимости (scopes).
Области видимости хранят ваши модели (данные), передают их в контроллеры и дают все возможности представлениям (views).
Области видимости в Angular оперируют общими идеями областей видимости в Javascript.

Первая область видимости, которую мы ожидаем, преназначена для всего приложения.
Инициализировать его можно с помощью ng-app атрибута:

<html ng-app="myApp">
    <head></head>
    <body></body>
</html>

Обратите внимание, что мы дали нашему приложению имя myApp. Теперь мы можем использовать его по всему html файлу.

Заметка: Вы можете инициализировать приложение без имени. Просто напишите <html ng-app>.

Вторая область видимости: ng-controller, она определяет где может действовать контроллер.
Мы можем иметь несколько контроллеров в нашем приложении. Каждый контроллер имеет свою область видимости.
Например, у меня есть файл index.html, код вы сможете найти ниже. Он отвечает за контроллер InboxCtrl.

<div ng-controller="InboxCtrl">
    <!-- область видимости InboxCtrl -->
</div>

И ng-app, и ng-controller — это директивы. Понимайте директивы в Angular, как нечто, что может расширить ваш HTML.

Ваш первый контроллер: небольшой пример

Применим на практике всю полученую ранее информацию.

<script src="lib/jquery-v1.11.1.js"></script>
<script src="lib/angular-v1.2.22.js"><script>
<div ng-controller="TestCtrl"><h1>{{title}}</h1>
   <input type="text" ng-model="title">
</div>

Через минуту мы объясним что такое ng-model="title".

<script>
   function TestCtrl($scope) {
      $scope.title = 'Write a title here...';
   };
</script>

Заметили, что имя функции совпадает со значением ng-controller? Angular ищет функцию с таким же именем и она может выполнять роль контроллера.

Итоговый результат:

<!doctype html>
<html ng-app>
  <head>
    <title>Sample AngularJS Controller</title>
  </head>
  <body>
    <div ng-controller="TestCtrl">
        <h1>{{title}}</h1>
        <input type="text" ng-model="title">
    </div>

    <script src="lib/jquery-v1.11.1.js"></script>
    <script src="lib/angular-v1.2.22.js"></script>

    <script>
      function TestCtrl($scope) {
        $scope.title = 'Write a title here...';
      };
    </script>
  </body>
</html>

** Исходники примера: 02-sample-controller **

ngView и маршрутизация

Свяжем URL c отображением:

<html ng-app="myApp">
    <head>
    </head>
    <body>
        <div ng-view></div>
    </body>
</html>

С помощью атрибута ng-view мы указываем Angular, где нужно отобразить контент, который меняется в зависимости от URL.

В нашем примере мы хотим видеть папку входящих сообщений. Когда пользователь посещает /inbox,
наш (еще не созданный) inbox.html файл должен быть отображен в ng-view.
В файл inbox.html мы подключаем соответствующий контроллер (InboxCtrl).

Angular отлично подходит для разработки одностраничных приложений, это значит,
что сайт никогда не будет перезагружен полностью, а другие страницы будут подтягиваться через XHR (Ajax).
Естественно это не полноценные станицы, а только представления, которые загружаются в зависимости от URL или окружения.

Модули

Взглянем на angular.module()

Любому приложению требуется модуль. Angular предоставляет совместимость пространства имем через angular.module().
Этот метод позволяет как установить, так и получить зависимость. Для создания (установки) нового модуля, нужно сделать так:

angular.module('myApp', []);

Обратите внимание на пустой массив, в нем мы можем указать другие модули или зависимости.
Тут мы создали модуль с названием myApp без зависимостей.

Чтобы получить ссылку на модуль для регистрации контроллеров, фабрик, директив и других компонентов, мы не указываем второй параметр:

angular.module('myApp');

Заметка: Когда мы инициализируем приложение с помощью ng-app="myApp" в html, значение должно совпадать с названием модуля.

Другой способ получить ссылку на модуль, это сохранить её в переменную при создании.
Но этот метод не рекомендуется, т.к может привести к потенциальным проблемам с глобальными переменными.

var app = angular.module('myApp', []);

Маршрутизация и внедрение зависимостей

Маршрутизация

Следющим шагом будет настройка маршрутизации, контролирующей какое представление будет использовано в зависимости от введенного URL.
Например, мы находимся в /inbox и хотим чтобы подтягивался inbox.html с соответствующих контроллером.
Мы можем использовать $routeProvider для этого.

Route Provider

Начиная с Angular 1.2.0 $routeProvider не включен в ядро, поэтому мы должны его подключать как отдельный модуль.

Для загрузки $routeProvider (явлется частью angular-route), нужно перейти на AngularJS.org, нажать кнопку Download,
выбрать нужную вам версию фреймворка и перейти по ссылке “Browse additional modules”.
Там вы сможете найти файл angular-route*.js, который мы добавим в наше приложение:

<body>
    <!-- ... -->
    <!-- Extra routing library -->
    <script src="lib/angular-route-v1.2.22.js"></script>
</body>

Заметка: версия angular-route должна соотвествовать версии основной библиотеки Angular.

Данный файл даст нам доступ к дополнительному модулю ngRoute, который нужно подключить к нашему приложению, указав как зависимость:

var app = angular.module('app', [
   'ngRoute'
]);

Настройка

В предыдущих частях мы видели как забиндить контроллер через DOM (или на view), но это только один из путей.
Angular позволяет динамически назначать контроллеры с помощью $routeProvider, что более гибко.
Теперь будем использовать этот метод, вместо биндинга через ng-controller атрибут.

В Angular есть .config() метод, в него мы передаем функцию, которая вызывается раньше многих других при старте приложения.
Тут мы можем настроить наши маршруты:

app.config(function () {/*...*/});

Внедрение зависимостей (DI)

Нам нужен доступ к $routeProvider, чтобы настроить маршруты внутри .config() функции,
это становится возможным благодоря некой “магии” Angular.
Мы можем легко получить доступ к $routeProvider,
просто указав его аргументом в config-функции и Angular поймет что нам требуется:

app.config(function ($routeProvider) {
   /* теперь мы можем использовать $routeProvider! :D */
});

Настройка маршрутизации

Теперь мы можем настроить маршруты с помощью $routeProvider и .config():

app.config(function ($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'
      });
});

$routeProvider очень легко использовать, мы просто указываем какой шаблон представления нужно использовать при соответствующем URL.
Работая рука об руку с атрибутом ng-view, который мы использовали ранее в index.html, шаблоны будут внедрены в соответствии с настройками.

В нашем приложении есть представление для списка входящих писем и представление для промотра информации о конкретном письме.

Первое отображение будет внедрено по URL /inbox, второе по /inbox/email/:id.
Вы могли заменить в конце второго пути :id — это динамическое представление.
Из URL в отображение будет передан параметр. Потом мы будем использовать его, чтобы составить запрос к серверу.

В описании каждого представления вы могли увидеть указание собственного контроллера.
Поздние версии Angular (а мы используем одну из последних) реализуют ControllerAs синтаксис,
который позволяет указать псевдоним контроллеру, для использования внутри представления.
Для InboxCtrl мы выбрали псевдоним inbox.

Заметка: вы можете использовать ControllerAs при обьявлении через атрибут: ng-controller="InboxCtrl as inbox".

Контроллеры

Соеденим модель и представление

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

app.controller('InboxCtrl', function () {
   // Связывание модели и представления
   // Небольшой хелпер, который нигде больше не пригодится
});

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

Запомните: хороший контроллер имеет минимум логики.

Каждый контроллер имеет доступ к $scope, это самый частоиспользуемый способ передачи параметров и методов в представление.
Помните как мы выяснили, что атрибут ng-controller определяет область видимости для HTML?
Так вот эта область видимости имеет доступ к $scope, который мы используем в контроллере.

$scope не единственный способ передачи данных в представление. Многие разработчики используют комбинацию ControllerAs синтаксиса и обращения к объекту this. Чтобы не усложнять данное руководство, мы остановимся на использовании $scope и болье не будем возвращаться к ControllerAs синтаксису.

app.controller('InboxCtrl', function ($scope) {
   // Инициализируем свойство title, чтобы использовать его в представлении
   $scope.title = "This is a title";
});

Потом мы можем использовать наш параметр:

<div ng-controller="InboxCtrl">
   {{ title }}
</div>

Заметка: В данном случае мы обратились к свойствую title напрямую, я бы советовал всегда использовать точку . в свойствах представления. Например, через передачу свойств в массиве.

Чтобы иметь возможность повторного использования контроллер, мы должны получать данные через сервис или фабрику.
Об этом в следующей части.

** Исходники примера: 03-application-controller **

Фабрики

Что такое фабрика?

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

Вывод: Если вы хотите обращаться к RESTful API, делайте это в фабрике!
Если вы хотите хранить “CurrentUser” с информацией об авторизации, сделайте это в фабрике!

Заметка: Вы могли слышать о таком шаблоне проектирования во многих языках программирования, но фабрики в Angular выполняют несколько иную роль.

Вы можете создать фабрику, используя метод angular.factory() таким образом:

app.factory('ExampleFactory', function ExampleFactory($rootScope, $http, $location) {
   return function myReusableFunction() {
      // выполняем что-то
   };
});

Обратите внимание, что мы внедрили несколько зависимостей: $rootScope, $http, $location.

Хорошей практикой является создание объекта внутри фабрики и потом уже его возврат.
Это позволяет использовать явное именование, а так же создавать приватные методы и переменные (замыкания).

В нашем приложении нам требуется получение сообщений, давайте создадим метод для этой задачи.
Angular использует $http сервис для обращения к серверу, поэтому внедрим его:

app.factory('InboxFactory', function InboxFactory ($http) {
   var exports = {};

   exports.getMessages = function () {
      return $http.get('json/emails.json')
         .error(function (data) {
            console.log('Произошла ошибка!', data);
      });
   };

   return exports;
});

$http

Будем использовать $http для GET запроса к файлу "json/emails.json".
Так же мы установили стандартный обработчик ошибок, добавив его в цепочке после $http.get().
$http() возвращает обещание (promise), поэтому мы можем использовать API обещаний,
так же как $q.defer().promise объект с небольшими дополнениями: а именно методы error(fn) и success(fn).
Это синтаксический сахар для методов then(fn) и catch(fn), но специализированные под $http.
Не волнуйтесь, если это кажется чем-то сложным, мы более подробно разберемся с обещаниями в следующем разделе.

Заметка: Что такое синтаксический сахар? Простыми словами: синтаксический сахар позволяет реализовать ваши идеи кратчайшим путем.

Исходники примера: 04-inbox-factory

Обещания

Объединяем контроллер и фабрику

Общеания очень важны для Angular, они позволяют организовать функции, которые занимают много времени (например, HTTP запросы).
Обещания в Angular реализованы с помощью компонента $q, созданного на основе библиотеки Q.

Будем откровенными, $q немного странный зверь. Вот отличный пост, объясняющий работу обещаний.
Если вы хотите глянуть на другие практические примеры, посмотрите Angular документацию $q.

Вот как работают обещания:

Пример:

var deferred = $q.defer();
deferred.promise.then(
  function whenThingsGoSunny(){},
  function whenThingsDontGoSoSunny(){}
)

Первый метод, который мы передали, это функция success, второй — error.

Когда вы делаете HTTP запрос, Angular использует $http сервис, основанный на $q.

И как мы уже говорили, $q позволяет использовать метод then() для внедрения функций success и error.
Когда вы используете $http, вы делаете что-то похожее (только без .then())

$http({method: 'GET', url: '/someUrl'})
   .success(function(data, status, headers, config) {
      // этот коллбек будет вызван асинхронно
      // если ответ будет доступен
   })
   .error(function(data, status, headers, config) {
      // будет вызванн асинхронно
      // если сервер вернет ответ с ошибкой
   });

У нас есть способ получения данных по письмам в фабрике и контроллер:

app.controller('InboxCtrl', function($scope, InboxFactory) {
   InboxFactory.getMessages()
      .success(function(jsonData, statusCode) {
         console.log('The request was successful!', statusCode, jsonData);
         // Теперь добавим email сообщения в $scope контроллера
         $scope.emails = jsonData;
   });
});

InboxFactory передается параметром в контроллер, это реализуется с помощью внедрения зависимостей.

Мы вызываем getMessages() в фабрике, а потом используем success метод, чтобы добавить список писем в $scope и отобразит их пользователю.

Шаблонизатор

Генерируем отображение

Расмотрим файлы отображения, которые мы будем использовать.
Вы должны обратить внимание, что в шаблонах мы можем использовать выражения,
для действий над переменными напрямую из $scope. В выражениях мы можем использовать JavaScript.

<!-- JavaScript в выражении -->
<h1>{{ [ firstName, lastName ].join(" ") }}</h1>
<!-- Фильтр валюты примененный к произведению двух чисел -->
<div class="total-info">{{ cost * quantity | currency }}</div>
<!-- Тернарный оператор в выражении -->
<div class="budget">{{ ( total > budget ) ? "Too Expensive" : "You can buy it!" }}</div>

Angular обработает эти выражения и этот пример показывает их эффективность.

В первом случае мы создали массив и положили в него две переменные из $scope, а потом объеденили их с помощью пробела.

Во втором примере мы умножили два числа и применили к результату фильтр отображения валют.
Почитать больше про фильтры вы можете в документации.

В третьем выражении мы использовали тернарный оператор, для сравнения значения переменных total и budget из $scope,
для отображения соответсвующего сообщения.

Код контроллера мог бы быть таким:

$scope.firstName = "John";
$scope.lastName = "Doe";
$scope.cost = 1;
$scope.quantity = 2;
$scope.total = 3;
$scope.budget = 4;

Такое использование шаблонов важный аспект Angular, но мы можем не только выводить данные особым образом, но и создавать полностью новые элементы.
В Angular это называется директивами и у них могут быть свои шаблоны (как и другой HTML файл, так и строчка в конфигурации).

Исходники примера: 05-templating

Обзор директив

Новые HTML элементы

Директивы в Angular пользволяют создавать новые HTML элементы.
Это дает возможность многоразового использования данных, шаблонов и поведения.

В нашем отображении это будет выглядеть так:

<div id="someview">
   {{ data.scopePropertyAsUsual }}
   <my-custom-element></my-custom-element>
</div>

Где my-custom-element внедрит шаблон и логику нашей директивы.

app.directive('myCustomElement', function myCustomElement() {
   return {
      restrict: 'EA',
      replace: true,
      scope: true,
      template: [
         "<div>",
         "	<h1>My Custom Element's Heading</h1>",
         "	<p>Some content here!</p>",
         "	{{ ctrl.expression | json }},"
         "</div>"
      ].join(""),
      controllerAs: 'ctrl',
      controller: function ($scope) {
         this.expression = {
            property: "Example"
         }
      },
      link: function (scope, element, attrs) {}
   }
});

Как вы могли заметить, имя директивы в нижнеВерблюжьемРегистре (myCustomElement),
при том как html элемент разделен тире (my-custom-element). Это соглашение принято в Angular для именования директив.

Затем мы возвращаем JavaScript объект с различными свойствами, которые описывают работу директивы:

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

Директивы это не только настраивамые элементы, вы можете использовать их для любого вида дополнительной логики, особенно, если вам требуется многократно использовать код.

Фабрики и взаимодействие с директивами

Завершаем работу над директивой

В нашем демо-приложении так выглядит InboxFactory:

angular.module('EmailApp')
   .factory('InboxFactory', function InboxFactory ($q, $http, $location) {
      'use strict';
      var exports = {};

      exports.messages = [];

      exports.goToMessage = function(id) {
         if ( angular.isNumber(id) ) {
            // $location.path('inbox/email/' + id)
         }
      }

      exports.deleteMessage = function (id, index) {
         this.messages.splice(index, 1);
      }

      exports.getMessages = function () {
         var deferred = $q.defer();
         return $http.get('json/emails.json')
            .success(function (data) {
               exports.messages = data;
               deferred.resolve(data);
            })
            .error(function (data) {
               deferred.reject(data);
            });
         return deferred.promise;
      };

      return exports;
   });

И вот так выглядит завершенный код директивы:

app.directive('inbox', function () {
   return {
      restrict: 'E',
      replace: true,
      scope: true,
      templateUrl: "js/directives/inbox.tmpl.html",
      controllerAs: 'inbox',
      controller: function (InboxFactory) {
         this.messages = [];
         this.goToMessage = function (id) {
            InboxFactory.goToMessage(id);
         };
         this.deleteMessage = function (id, index) {
            InboxFactory.deleteMessage(id, index);
         };
         InboxFactory.getMessages()
            .then( angular.bind( this, function then() {
               this.messages = InboxFactory.messages;
            })	);
      },
      link: function (scope, element, attrs, ctrl) {
         /*
         по сошлашению мы не используем префикс $ в link функции
         чтобы явно указать на фиксированный порядок аргументов
         */
      }
   }
});

Теперь мы можем использовать эту директиву в любом месте приложения, добавлением элемента <inbox></inbox>.

Когда приложение будет запущено, Angular заменит <inbox> на шаблон, указанный в templateUrl.
Мы задали псевдоним inbox для контроллера. Если вы посмотрите в шаблон, то выражения выглядят как inbox.messages и inbox.deleteMessage(id, $index).
Это тоже самое, что this.messages и this.deleteMessage в InboxFactory.

И, наконец, у нас есть link фунция, которая запускается сразу после запуска контроллера.
Эта функция получает связанный контроллер четверым аргументом, в данном случае мы назвали аргумент ctrl.
Да, все верно, link функция имеет фиксированый порядок аргументов (например, $scope всегда идет первым).

Это финальный HTML шаблон (отображение) для директивы:

<div class="inbox">
  <div class="inbox__count">
    You have {{ inbox.messages.length && inbox.messages.length || 'no' }} messages
  </div>
  <div ng-hide="!inbox.messages.length">
    <input type="text" class="inbox__search"
      ng-model="inbox.search"
      placeholder="Search by 'from', e.g. TicketMaster">
  </div>
  <ul class="inbox__list">
    <li ng-show="!inbox.messages.length">
      No messages! Try sending one to a friend.
    </li>
    <li ng-repeat="message in inbox.messages | filter:{ from: inbox.search }">
      <div class="inbox__list-info">
        <p class="inbox__list-from"> from: {{ message.from }} </p>
        <p class="inbox__list-date"> {{ message.date | date: 'dd/MM/yyyy' }} </p>
        <p class="inbox__list-subject"> {{ message.subject }} </p>
      </div>
      <div class="inbox__list-actions">
        <a href="#" ng-click="inbox.goToMessage(message.id);">
          Read
        </a>
        <a href="" ng-click="inbox.deleteMessage(id, $index);">
          Delete
        </a>
      </div>
    </li>
  </ul>
</div>

Обратите внимание на использование таких директив, как ng-hide, ng-show, ng-click, ng-repeat и ng-model.
Мы не хотим детально рассматривать эти директивы, но вы можете изучить документацию.

Исходники примера: 06-first-directive

Встроенные директивы

ng-show, ng-repeat и ng-click

В Angular “из коробки” доступны действительно полезные директы, такие как ng-show, ng-repeat и ng-click.
Существует множество встроенных директив и новые добавляются с каждым релизом.

Эти директивы опираются на данные. Например ng-show="someData" покажет элемент,
когда значением someData будет true. Если значением будет false, директива скроет элемент.

Одна из самых мощных директив в Angular — это ng-repeat, которая перебирает обьекты (или элементы) в массиве:

<ul>
   <li ng-repeat="item in items">
      {{ item.name }}
   </li>
</ul>

В нашем приложении вы могли заметить, как мы перебираем сообщения и фильтруем их на основании поля для ввода search.

<li ng-repeat="message in inbox.messages | filter:{ from: inbox.search }">

Но откуда взялась переменная inbox.search? Она была создана директивой ng-model, прикрепленной к input элементу:

<input type="text" class="inbox__search" placeholder="Search" ng-model="inbox.search">

Теперь, когда кто-то что-то введет в это поле, значение будет присвоено перменной inbox.search и будет обовлен фильтр для ng-repeat.

Все ng- атрибуты — это директивы, созданные таким же образом, как это ранее делали мы. Их можно использовать в любом месте приложения.

Исходники примера: 07-ng-repeat

Маскировка

В Angular есть действительно аккуратная встроеная возможность указать на элемент, который требуется замаскировать, пока он рендерится.
Маскировка позволяет скрыть любой элемент и показать лоадер, пока Angular сгенерирует отображение и загрузить данные.

Когда lib/angular.js загружен, Angular автоматически добавляет строковый <style></style> в документ, где описывает стили для маскировки.
Загрузка библиотеки и зависимойстей тоже может занять время, поэтому я рекомендую вручную добавить эти стили в <head>:

<!doctype html>
<html ng-app="app">
  <head>
    <style>
    [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
      display: none !important;
    }
    </style>
  </head>
  <body>
  </body>
</html>

Это на 100% надежно и скроет элементы незамедлительно, без инициализации Angular.
А вот так указывается элемент для маскировки:

<div ng-cloak>
   {{ someData }}
</div>

Отладка

Отладка приложений на Angular может быть очень сложной и утомительной. В этом разделе будет дана основа для упрощения этого процесса.

Имена функций в цепочке вызовов

В Angular очень часто используются анонимные функции. Когда в них возникает ошибка, чтобы упростить отладку, я именую такие функции.

Было:

app.factory('InboxFactory',
   function ($q, $http) {}
);

Стало:

app.factory('InboxFactory',
   function InboxFactory ($q, $http) { }
);

После этого в цепочке вызовов будет отображаться InboxFactory, вместо anonymous.
Очень удобная мелочь для дебаггинга приложений на Angular.

Ошибки маршрутизации

Когда работаете с маршрутизатором, можно слушать события (Routing Events) в $rootScope.
Удобное место для этого в функции run, больше информации в документации.

app.run(function($rootScope) {
   $rootScope.$on('$routeChangeError', function(event, current, previous, rejection) {
      console.log(event, current, previous, rejection)
   })
});

В этом примере мы установили слушателя для ошибок, произошедших при смене маршрута.

Отладка областей видимости

Вот отличная статья по отладке scopes.

Завершаем проект

Работающее Email приложение

Вы можете создать свое приложение, следуя этому видео-руководству:

Мы сделали ряд изменений, чтобы приложение имело завершенный вид:

  1. Добавили новый маршрут для каждого сообщения, принимающий параметр :id
  2. Добавили CSS стили
  3. Начали использовать ControllerAs синтиксис в наших контроллерах
  4. Добавили ngSanitize библиотеку, для использования ng-bind-html директивы
  5. Раскомментировали метод goToMessage в InboxFactory
  6. Упростили контроллеры
  7. Добавили Email контроллер и отображение
  8. Добавили Email фабрику
  9. Добавили Email директиву и ее шаблон
  10. Добавили JSON файлы для каждого сообщения
  11. Добавили ng-cloak

Я бы посоветовал прочитать весь код и разобраться в происходящем.
Вы, наверняка, найдете более удобные пути для реализации тех же задач.
Это просто учебный пример, но мы раскрываем пункты, с которых можно начать работу.

Исходники приложения: final-section

  • LinkedIn
  • Tumblr
  • Reddit
  • Google+
  • Pinterest
  • Pocket