Знакомство с контроллерами и директивами в 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.

Subscribe to our Newsletter

Let us send you the best of what we've discovered in DevOps, Cloud and Kubernetes, as well us occasional event announcements.

We are also preparing some ways to learn together: weekly challenges, free courses and more. Subscribe now to be the first to get those.