Знакомство с контроллерами и директивами в Angular.js

Illustration of a person sitting on the floor, leaning against a wall, engrossed in reading a tablet, highlighted by a dual-tone background. Illustration of a person sitting on the floor, leaning against a wall, engrossed in reading a tablet, highlighted by a dual-tone background.

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

Контроллеры в Angular.js являются связующим звеном между html-разметкой и "моделью". В Angular за модель отвечает сразу 4 разных вида объектов, которые мы рассмотрим позже. Опустим пока что модель и будем хранить все данные прямо в контроллере.

Задачей контроллера, как и в других MVC фреймворках, является получение данных из модели и вывод их пользователю, или же, в другом направлении, получение данных из представления и отправка их в модель.

В первую очередь, удалим src/app/main.controller.js, src/app/main.controller.spec.js, src/app/main.html и src/components/navbar/, которые для нас сгенерировал Yeoman.

Пишем первый контроллер

Наш первый контроллер будет называться TransactionsCtrl. Согласно соглашениям о наименовании в Angular.js, имя контроллера строится как "имя + Ctrl".

Создадим файл src/app/main/transactions.controller.js. В этом файле определим сам контроллер:

//src/app/main/transactions.controller.js
angular.module("ngmkdev").controller('TransactionsCtrl', function($scope) {

});

Заметьте, что нам не нужно подключать свежесозданный файл вручную при помощи тега <script>. Когда мы запустим gulp serve, Gulp начнёт мониторить изменения в файлах и папках и обновлять временный index.html внутри папки .tmp/ (он, кстати, и загружается когда вы открываете в браузере localhost:4567). А когда придёт время собирать финальную версию приложения, то команда gulp build сделает тоже самое и вернёт нам необходимый index.html с ужатыми и подключенными нужными библиотеками и файлами приложения.

На данном этапе мы не будем разбивать вёрстку приложения на несколько файлов и напрямую отредактируем src/index.html (поэтому мы и удалили папку components/navbar/ и main.html).

В файле index.html заменим <div ui-view></div> на следующий простой макет:

<!-- src/index.html -->
<div ui-view class="container">
  <div class="col-xs-2">
    <h2>Деньги</h2>
    <h3 class="money-ok">
      500.0
    </h3>
    <ul class="nav nav-pills nav-stacked">
      <li>
        <a href="#"><i class="glyphicon glyphicon-th-list"></i> Транзакции</a>
      </li>
      <li>
        <a href="#"><i class="glyphicon glyphicon-tasks"></i> Настройки</a>
      </li>
    </ul>
  </div>
  <div class="col-xs-6">
    <h2>Транзакции</h2>

    <br>
  </div>
</div>

Обратите внимание: мы ещё не потратили ни секунды на ручное подключение Boostrap, но зато сразу перешли к его использованию. Многие рутинные процессы разработки автоматизированы: нам нужно только сверстать сам макет. О подключении файлов позаботится Gulp, а о красивом внешнем виде – Bootstrap.

Теперь нужно определить этот контроллер на каком-нибудь html-элементе в нашем приложении, чтобы внутри него был доступ к этому контроллеру и мы могли использовать данные из контроллера внутри html.

Лучше всего для этого подходит центральный блок с транзакциями. Определим контроллер на родительском блоке при помощи директивы ng-controller:

<!-- src/index.html -->
<div class="col-xs-6" ng-controller="TransactionsCtrl as transactions_ctrl">
   <h2>Транзакции</h2>
   <br>
</div>

Директивы в Angular.js отвечают за добавление новой функциональности html-разметке. Именно через директивы идёт связь вашего js-кода с интерфейсом. ng-controller – одна из самых простых и важных директив. Она связывает часть html кода с конкретным контроллером, делает доступным $scope этого контроллера внутри этого html элемента.

Что такое $scope?

$scope – это объект, через который мы можем получить в html коде доступ к данным из модели или контроллера. В Angular.js реализована сложная система обновления и наследования скоупов. Самым главным является $rootScope, который определяется на всём приложении и доступен из любой части кода. Контроллеры создают свой $scope, который доступен внутри html-элемента, на котором этот контроллер определён.

Начиная с версии 1.2, в Angular.js появился новый синтаксис для доступа к контроллеру из html: указав as transactions_ctrl на $scope этого элемента определяется новый объект, соответствующий контроллеру. Всё, что мы определим в TransactionsCtrl контроллере через this будет доступно через transactions_ctrl. Таким образом решается проблема вложенных $scope – мы всегда знаем с каким именно скоупом мы работаем.

Посмотрим это поведение на примере. Определим для контроллера аттрибут transactions следующим образом:

//src/app/main/transactions.controller.js
angular.module('ngmkdev').controller('TransactionsCtrl', function($scope) {
  this.transactions = [
    { amount: 500.00, date: "08/08/2014", description: "Подписка на журнал" },
    { amount: 150.00, date: "07/08/2015", description: "Кокаин" }
  ]
});

И выведем транзакции при помощи ещё одной директивы – ng-repeat, ответственной за вывод коллекций. Её синтаксис говорит сам за себя.

<!-- src/index.html -->
<div class="col-xs-6" ng-controller="TransactionsCtrl as transactions_ctrl">
  <h2>Транзакции</h2>
  <br>
  <table class="table table-striped">
    <thead>
      <tr>
        <th>Сумма</th>
        <th>Дата</th>
        <th>Описание</th>
        <th>Действия</th>
      </tr>
    </thead>
    <tbody>
      <tr ng-repeat="transaction in transactions_ctrl.transactions">
        <td>{{transaction.amount}}</td>
        <td>{{transaction.date}}</td>
        <td>{{transaction.description}}</td>
        <td></td>
      </tr>
    </tbody>
  </table>
</div>

Чтобы вывести что-нибудь из контроллера на экран пользователю используются фигурные скобки. Angular.js не использует существующие шаблонизаторы вроде Handlebars или Mustache. Вместо этого прямо в фреймворк встроен свой шаблонизатор.

Теперь в браузере вы должны увидеть таблицу из двух транзакций:

Но нам, конечно, нужно больше – нам необходим способ добавления новых транзакций. Для этого сверстаем простенькую форму, вставим её сразу после заголовка <h2>Транзакции</h2>:

<!-- src/index.html -->
<form class="form-inline">
  <div class="form-group">
    <div class="input-group">
      <div class="input-group-addon">$</div>
      <input class="form-control" type="text" placeholder="Сумма">
    </div>
  </div>
  <div class="form-group">
    <label class="sr-only"></label>
    <input type="text" class="form-control" placeholder="Описание">
  </div>
  <button type="submit" class="btn btn-default">Добавить</button>
</form>

Добавим заготовку для новой транзакции в контроллер:

// src/app/main/transactions.controller.js

// ...
  this.resetTransaction = function() {
    this.newTransaction = {
      amount: 0.0,
      date: "01/02/1993",
      description: null
    }
  }
  this.resetTransaction();
// …

Сразу после инициалиации контроллера текущая транзакция будет равна заготовке из функции resetTransaction().

Теперь необходимо связать форму с объектом newTransaction. Для этого мы будем использовать директиву ng-model, которая связывает элемент формы с каким-нибудь атрибутом контроллера. Изменённые инпуты примут вид:

<!-- src/index.html -->
<!-- ... -->
<input class="form-control"
       type="text"
       placeholder="Сумма"
       ng-model="transactions_ctrl.newTransaction.amount">
<!-- ... -->
<input type="text"
       class="form-control"
       placeholder="Описание"
       ng-model="transactions_ctrl.newTransaction.description">
<!-- ... -->

Остаётся теперь добавить обработку отправки формы. К счастью, и для этого в Angular.js есть встроенная директива ng-submit. Форма принимает вид:

<!-- src/index.html -->
<form class="form-inline" ng-submit="transactions_ctrl.addTransaction()">
<!-- ... -->
</form>

Добавим метод addTransaction() в TransactionsCtrl:

// src/app/main/transactions.controller.js
// ...
this.addTransaction = function() {
  this.transactions.push(this.newTransaction);
  this.resetTransaction();
}
// ...

Всё готово! Открываем браузер, заполняем форму, жмём энтер или кнопку на форме и наша таблица автоматически обновляется: новая транзакция появляется внизу таблицы. Обратите внимание: мы нигде не писали код обновления таблицы. Angular.js сам понимает, что при изменении массива transactions нужно обновить соответствующий этому массиву html-код. Это одна из самых классных фич Angular.js – автоматическое обновление и двусторонняя связь между html-кодом и кодом приложения.

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

  1. Избавьтесь от указанных прямо в коде транзакций.
  2. Отсортировать транзакции по убыванию даты транзакции – сверху самые последние, чем ниже тем старее.

Форкните репозиторий с приложением и реализуйте эти задачи. Весь код этой статьи находится в коммите 12aacf6 В следующей статье мы научимся использовать сервисные объекты и узнаем про dependency injection в Angular.js.