Как сделать динамическую веб-страницу с помощью AngularJS


Шаг 1. Переключите репу на состояние для step 1:
git checkout -f step-1

Шаг 2. Сначала создадим статический контент, а потом сделаем его динамичным. Внесите в app/index.html созданный в предыдущем уроке такие изменения. Должно получиться так:

<!doctype html>
<html lang="en" ng-app>
<head>
  <meta charset="utf-8">
  <title>Google Phone Gallery</title>
  <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css">
  <link rel="stylesheet" href="css/app.css">
  <script src="bower_components/angular/angular.js"></script>
</head>
<body>

  <ul>
    <li>
      <span>Nexus S</span>
      <p>
        Fast just got faster with Nexus S.
      </p>
    </li>
    <li>
      <span>Motorola XOOM™ with Wi-Fi</span>
      <p>
        The Next, Next Generation tablet.
      </p>
    </li>
  </ul>

</body>
</html>

Можете добавить еще статического контента, например <p>Total number of phones: 2</p>.

Шаг 4. Переключите репу на состояние для step 2:
git checkout -f step-2

Шаг 5. Существует много способов для структуризации кода приложения. В Angular преимущественно используется паттерн проектирования Model-View-Controller (MVC). Рассмотрим как добавить в Angular-приложение модель, представление и контроллер. Решим задачу динамической генерации списка телефонов на основе данных. Для того чтобы решить эту задачу мы внесем следующие изменения.

В Angular представление (view) это проекция модели на HTML-шаблон. Это означает, что как только модель меняется, Angular обновляет в соответствующих точках биндинги.

Изменим app/index.html следующим образом:

<!doctype html>
<html lang="en" ng-app="phonecatApp">
<head>
  <meta charset="utf-8">
  <title>Google Phone Gallery</title>
  <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css">
  <link rel="stylesheet" href="css/app.css">
  <script src="bower_components/angular/angular.js"></script>
  <script src="js/controllers.js"></script>
</head>
<body ng-controller="PhoneListCtrl">

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

</body>
</html>

По сравнению с предыдущим кодом, мы заменили статическую часть на директиву ngRepeat и два Angular-выражения. Директива ngRepeat в данном случае создает по <li> элементу на каждый телефон. А в выражениях просто подставляют соответствующие значения у этого телефона.

Другая директива ng-controller цепляет контроллер PhoneListCtrl к тегу <body>. Выражения {{phone.name}} и {{phone.snippet}} представляют собой биндинги ссылающиеся на модель данных приложения. Так вот модель устанавливается в PhoneListCtrl контроллере.

Обратите внимание что у атрибута ng-app появилось значение phonecatApp. Таким образом реализуется модульностьphonecatApp - это имя модуля. Данные модуль содержит контроллер PhoneListCtrl.


Шаг 6. Рассмотрим теперь контроллер в файле app/js/controllers.js:

'use strict';

/* Controllers */

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

phonecatApp.controller('PhoneListCtrl', function($scope) {
  $scope.phones = [
    {'name': 'Nexus S',
     'snippet': 'Fast just got faster with Nexus S.'},
    {'name': 'Motorola XOOM™ with Wi-Fi',
     'snippet': 'The Next, Next Generation tablet.'},
    {'name': 'MOTOROLA XOOM™',
     'snippet': 'The Next, Next Generation tablet.'}
  ];
});

Тут объявляется контроллер PhoneListCtrl и регистрируется в модуле phonecatApp. Контроллер играет ключевую роль в привязке данных к представлению.

Контроллер PhoneListCtrl цепляет данные о телефонах к переменной $scope которая внедряется в функцию нашего контроллера. $scope - это область видимости контроллера, которая доступна всем биндингам расположенным в теге <body ng-controller="PhoneListCtrl">. Данная область видимости унаследована (prototypical descendant) от корневой области видимости (root scope), которая создается при загрузке приложения.

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

Шаг 7. Т.к. контроллер отделен от представления, то его легко можно протестировать. Посмотрим как создать модульный тест (unit test) в Angular.

Если наш контроллер определен в глобальном пространстве имен, то мы можем просто создать его экземпляр подсунув ему фиктивную (mock) область видимости.

describe('PhoneListCtrl', function(){

  it('should create "phones" model with 3 phones', function() {
    var scope = {},
        ctrl = new PhoneListCtrl(scope);

    expect(scope.phones.length).toBe(3);
  });

});

Данный тест создает экземпляр контроллера PhoneListCtrl и удостоверяется в том, что массив телефонов из области видимости содержит 3 записи.

На практике обычно не выносят функцию контроллера в глобальное пространство имен. Вместо этого мы её зарегистрировали через анонимную функцию в конструкторе контроллера для модуля phonecatApp. Для такого случая Angular предоставляет сервис $controller посредством которого можно получить контроллер по имени.

Рассмотрим файл test/unit/controllersSpec.js

'use strict';

/* jasmine specs for controllers go here */
describe('PhoneCat controllers', function() {

  describe('PhoneListCtrl', function(){

    beforeEach(module('phonecatApp'));

    it('should create "phones" model with 3 phones', inject(function($controller) {
      var scope = {},
          ctrl = $controller('PhoneListCtrl', {$scope:scope});

      expect(scope.phones.length).toBe(3);
    }));

  });
});

  • Перед каждым тестом загружается модуль phonecatApp;
  • В тестовую функцию внедряется сервис $controller;
  • $controller используется для создания экземпляра PhoneListCtrl;
  • Созданный экземпляр используется для верификации того что массив телефонов в области видимости содержит три записи.

Шаг 8. Наверняка вы обратили внимание на необычный синтаксис тестов на предыдущем шаге. Это синтаксис фреймворка Jasmine (документация) для behavior-driven разработки (BDD).

Проект angular-seed преднастроен для запуска тестов с помощью Karma. Но надо сначала убедиться, что Karma и плагины установлены, для этого надо выполнить команду: npm install.

Для запуска тестов надо выполнить команду npm test.

Karma автоматически запускает новые окна браузеров Chrome и Firefox. Просто игнорируйте их и позвольте им работать в фоновом режиме. Karma будет использовать запущенные браузеры для выполнения тестов. Не сворачивайте браузеры, которые запустила Karma потому что на некоторых ОС память назначенная на свернутые браузеры лимитирована, что может сильно замедлить тестирование.

Если у вас установлен только один браузер (например Chrome или Firefox), то обновите конфигурационный файл test/karma.conf.js перед запуском тестов. Например, если у вас установлен только Chrome, то оставьте только его в списке браузеров:

  ...
  browsers: ['Chrome'],
  ...

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

  info: Karma server started at http://localhost:9876/
  info (launcher): Starting  browser "Chrome"
  info (Chrome 22.0): Connected on socket id tPUm9DXcLHtZTKbAEO-n
  Chrome 22.0: Executed 1 of 1 SUCCESS (0.093 secs / 0.004 secs)
Yay! The test passed! Or not...

Для того чтобы перезапустить тесты надо просто изменить исходники или тесты в общем .js файлы. Karma это засечет и перезапустить тесты.