Тип Поста

Компонентный подход к созданию приложения с помощью AngularJS 1.5 & 2.0

Компонентный подход к созданию приложения с помощью AngularJS 1.5 & 2.0

С каждым релизом AngularJS, создатели этого фреймворка стараются сократить различия между версиями 1.x и 2.0. С выходом версии AngularJS 1.5, разработчики могут создавать приложение структурно похожее на AngularJS 2.0.

В этой статье мы напишем директиву, реализующую вывод данных в нескольких колонках с помощью версии AngularJS 1.4. Затем мы пройдем все необходимые шаги для обновления кода до версии 1.5, и после этого, взглянем на то, как преобразовать полученный код в версию 2.0.

Приступим к созданию

Начнем с того, что для начала создадим директорию для проекта и назовём её AngularMigrateApp. Внутри неё создадим файл, который будет входной точкой нашего приложения – index.html. И поместим в него такой код:

<!DOCTYPE html>
<html lang="ru" ng-app="myApp">
<head>
  <meta charset="UTF-8">
  <title>AngularJS App</title>

  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
</head>
<body>

  <script src="https://code.angularjs.org/1.4.10/angular.js"></script>
  <script src="js/app.js"></script>
</body>
</html>

Мы также воспользуемся CSS-фреймворком Bootstrap для разработки внешнего вида нашего приложения. Для удобства работы, просто подключим Bootstrap и AngularJS через CDN.

Создаем директиву

Давайте создадим простую директиву, которая будет отображать данные из массива объектов. Для начала создадим директорию с файлом js/app.js и в него поместим AngularJS модуль и контроллер с нужными данными:

angular.module( 'myApp', [] )
  
  .constant( 'employees', [ {
    firstName: 'Rima',
    lastName : 'George',
    location : 'San Francisco'
  }, {
    firstName: 'Shaun',
    lastName : 'John',
    location : 'Germany'
  }, {
    firstName: 'Rahul',
    lastName : 'Kurup',
    location : 'Bangalore'
  }, {
    firstName: 'Samson',
    lastName : 'Davis',
    location : 'Canada'
  }, {
    firstName: 'Shilpa',
    lastName : 'Agarwal',
    location : 'Noida'
  } ] )

  .controller( 'HomeCtrl', [ '', 'employees', function( , employees )
  {
    .employees = employees;
  } ] );

Мы определили константу с именем employees, которая содержит массив данных для примера. После этого, мы вставляем зависимость этого массива в контроллер HomeCtrl, и делаем его доступным в области видимости контроллера.

Теперь давайте создадим директиву с именем myGrid, с помощью которой выведем созданный массив:

.directive( 'myGrid', function()
{
  return {}
})

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

<my-grid></my-grid>

Для этого добавим настройки для директивы, которые ограничат область её применения:

.directive( 'myGrid', function()
{
  return {
    restrict: 'E'
  }
})

Затем мы передадим данные, содержащиеся в массиве employees из Вида в директиву. Для этого добавим следующую связь в нашу директиву:

.directive( 'myGrid', function()
{
  return {
    restrict: 'E',
    scope   : {
      info: '=info'
    }
  }
})

Теперь мы можем передавать данные из employees в директиву как атрибут:

<my-grid info="employees"></my-grid>

И наконец, нам понадобится HTML-шаблон для отображения данных:

.directive( 'myGrid', function()
{
  return {
    restrict   : 'E',
    scope      : {
      info: '=info'
    },
    templateUrl: 'tmpl/directiveGrid.html'
  }
})

Создадим в корне проекта директорию c файлом tmpl/directiveGrid.html и поместим в него такой код:

<div class="panel panel-primary">
  <div class="panel-heading">Колоночная директива</div>

  <table class="table">
    <thead>
      <tr>
        <th>Имя</th>
        <th>Фамилия</th>
        <th>Расположение</th>
      </tr>
    </thead>

    <tbody>
      <tr ng-repeat="employee in info">
        <td>{{ employee.firstName }}</td>
        <td>{{ employee.lastName }}</td>
        <td>{{ employee.location }}</td>
      </tr>
    </tbody>
  </table>
</div>

Как вы могли заметить выше, мы проходимся циклом через свойство info и выводим каждый элемент массива в списке employee.

Давайте используем myGrid директиву внутри index.html. Добавим следующий код:

<div ng-controller="HomeCtrl">
  <my-grid info="employees"></my-grid>
</div>

Мы определили HomeCtrl контроллер, внутри которого используется наша директива. Сохраним все проделанные изменения в редакторе кода, и запустим в браузере страницу index.html. Полученный результат будет таким:

See the Pen AngularJS Update App 1 by Sergey Bogdanov (@sbogdanov108) on CodePen.

Обновляем приложение до версии 1.5

Только что мы создали AngularJS директиву, используя 1.4 версию фреймворка и всё работает довольно таки хорошо. Теперь можно попробовать использовать тот же самый код с версией AngularJS 1.5 и посмотреть – будут ли ошибки?

Заменим в файле index.html ссылку на CDN, которая подключит версию 1.5. Если вы попробуете обновить страницу, то всё должно продолжить работать без ошибок. С каждым новым релизом 1.x версий, фреймворк становится всё ближе к AngularJS 2.0, который использует в своей работе компоненты и мы можем воспользоваться этим преимуществом для нашего кода, и сделать возможным переход к версии 2.0 как можно более легким.

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

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

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

.component( 'myDataComp', {} )

В отличии от директив, которые принимают функцию, в компонентный метод мы передаем объект. Этот объект будет содержать тот же самый шаблон, который мы использовали для директивы. Для этого создадим в директории файл tmpl/componentGrid.html:

<div class="panel panel-primary">
  <div class="panel-heading">Колоночный компонент</div>

  <table class="table">
    <thead>
    <tr>
      <th>Имя</th>
      <th>Фамилия</th>
      <th>Расположение</th>
    </tr>
    </thead>

    <tbody>
    <tr ng-repeat="employee in info">
      <td>{{ employee.firstName }}</td>
      <td>{{ employee.lastName }}</td>
      <td>{{ employee.location }}</td>
    </tr>
    </tbody>
  </table>
</div>

Изменим код компонента:

.component( 'myComp', {
  restrict   : 'E',
  scope      : {
    info: '=info'
  },
  templateUrl: 'tmpl/componentGrid.html'
})

Как видно из этого кода, мы должны создать все те же опции, что и в директиве.

Теперь создадим компонент и назовем его myComp, а затем добавим в файле index.html:

<div ng-controller="HomeCtrl">
  <my-grid info="employees"></my-grid>
  <my-comp info="employees"></my-comp>
</div>

Сохраните изменения и обновите страницу в браузере – данные не будут отображены, при этом никаких ошибок в консоли так же не будет. Разберемся, в чем же дело?

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

Поэтому, нам необходимо назначить опции bindings значение данных контроллера. Опция restrict больше не требуется, т.к. компоненты ограничены только тем элементом, к котором находятся.

Изменим наш код компонента:

.component( 'myComp', {
  bindings   : {
    info: '=info'
  },
  templateUrl: 'tmpl/componentGrid.html'
} )

Свойство info будет связано с данными из контроллера. Псевдонимом по умолчанию для контроллера является имя $ctrl и внутри шаблона мы будем использовать это значение для доступа к свойству info:

<div class="panel panel-primary">
  <div class="panel-heading">Колоночный компонент</div>

  <table class="table">
    <thead>
      <tr>
        <th>Имя</th>
        <th>Фамилия</th>
        <th>Расположение</th>
      </tr>
    </thead>

    <tbody>
      <tr ng-repeat="employee in $ctrl.info">
        <td>{{ employee.firstName }}</td>
        <td>{{ employee.lastName }}</td>
        <td>{{ employee.location }}</td>
      </tr>
    </tbody>
  </table>
</div>

Теперь, при обновлении страницы в браузере, вы должны увидеть вывод данных нашего приложения с помощью компонента myComp.

See the Pen AngularJS Update App 2 by Sergey Bogdanov (@sbogdanov108) on CodePen.

Обновление до версии 2.0

Замечание: на момент написания статьи, AngularJS находится в бета состоянии, поэтому используется версия Angular2.0.0-beta.9.

Давайте заменим используемую версию AngularJS в нашем приложении на версию 2.0 из CDN и посмотрим на возникшие ошибки:

<script src="https://code.angularjs.org/2.0.0-beta.9/angular2.js"></script>

После обновления страницы ничего не будет отображено и, если мы проверим консоль браузера, то увидим пару ошибок:

Uncaught ReferenceError: System is not defined(anonymous function)
Uncaught ReferenceError: angular is not defined

Как видите, код нашего компонента не работает с AngularJS 2.0.

Давайте начнем с самого начала и посмотрим, как же работает новая версия фреймворка и попробуем портировать наш компонент.

В отличии от AngulrJS версий 1.x, где можно было просто подключить фреймворк через script тег, процесс работы с AngularJS 2.0 немного изменился. Нам понадобится подключить пару-тройку дополнительных библиотек для работы c версией 2.0. Для процесса разработки приложения будет нормальным вариантом подключать дополнительные библиотеки раздельно через script тег, но для production-версии приложения их необходимо соединить в единый файл.

Если мы взглянем на рекомендации официального руководства по быстрому старту, то мы увидим, какие именно библиотеки и зависимости нам понадобятся для начала работы с AngularJS 2.0.

Давайте создадим отдельную директорию и назовём её AngularJS2.0Component, а затем создадим в ней файл package.json с такой конфигурацией:

{
  "name": "angular2-quickstart",
  "version": "1.0.0",
  "scripts": {
    "start": "npm run lite",
    "lite": "lite-server"
  },
  "license": "ISC",
  "dependencies": {
    "bootstrap": "^3.3.6",
    "angular2": "2.0.0-beta.8",
    "es6-promise": "^3.0.2",
    "es6-shim": "^0.33.3",
    "reflect-metadata": "0.1.2",
    "rxjs": "5.0.0-beta.2",
    "zone.js": "0.5.15"
  },
  "devDependencies": {
    "concurrently": "^2.0.0",
    "lite-server": "^2.1.0"
  }
}

В этом коде содержаться все необходимые зависимости для работы AngularJS 2.0 приложения. Сохраним изменения и установим требуемые зависимости через npm, выполнив команду в консоли:

npm install

Создадим поддиректорию app с файлом app.component.js, который будет содержать такой код:

(function( app )
{
  app.AppComponent =
    ng.core.Component( {
      selector   : 'my-comp',
      templateUrl: 'app/tmpl/grid.html'
    } )
      .Class( {
        constructor: function()
        {
          this.employees = [ {
            firstName: 'Rima',
            lastName : 'George',
            location : 'San Francisco'
          }, {
            firstName: 'Shaun',
            lastName : 'John',
            location : 'Germany'
          }, {
            firstName: 'Rahul',
            lastName : 'Kurup',
            location : 'Bangalore'
          }, {
            firstName: 'Samson',
            lastName : 'Davis',
            location : 'Canada'
          }, {
            firstName: 'Shilpa',
            lastName : 'Agarwal',
            location : 'Noida'
          } ];
        }
      } );
})( window.app || (window.app = {}) );

В этом коде мы используем Angular пространство имен с помощью метода ng.core для создания компонента. Также определили селектор для нашего компонента, назвав его my-comp. Затем мы будем использовать тот же самый HTML-шаблон, как и в предыдущих примерах, назвав его app/tmpl/grid.html. Наконец, нами был определен объект employees в конструкторе компонента.

Создадим файл app/main.js и скопируем в него такой код:

(function( app )
{
  document.addEventListener( 'DOMContentLoaded', function()
  {
    ng.platform.browser.bootstrap( app.AppComponent );
  } );
})( window.app || (window.app = {}) );

Этот код инициализирует компонент, который был нами создан.

Далее, создадим в корне проекта входную точку приложения, которая будет файлом index.html и поместим в него следующий код:

<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="UTF-8">
  <title>AngularJS 2.0 App</title>

  <link rel="stylesheet" type="text/css" href="node_modules/bootstrap/dist/css/bootstrap.min.css">
  
  <!-- 1. Подключение библиотек -->
  <!-- Для работы IE требуется полифил -->
  <script src="node_modules/es6-shim/es6-shim.min.js"></script>
  <script src="node_modules/angular2/es6/dev/src/testing/shims_for_IE.js"></script>
  <script src="node_modules/angular2/bundles/angular2-polyfills.js"></script>
  <script src="node_modules/rxjs/bundles/Rx.umd.js"></script>
  <script src="node_modules/angular2/bundles/angular2-all.umd.js"></script>
  
  <!-- 2. Подключаем наши модули -->
  <script src="app/app.component.js"></script>
  <script src="app/main.js"></script>
</head>

<!-- 3. Выводим приложение -->
<body>

  <my-comp>Загрузка...</my-comp>

</body>
</html>

Файл index.html является начальным шаблоном для AngularJS 2.0 приложения. Мы подключили в нём все необходимые зависимости и внутри body используем наш компонент.

Сохраним изменения и запустим сервер командой npm start. Эта команда стартует локальный сервер для разработки – lite-server, который будет обрабатывать index.html в браузере.

Но всё же данные нашего приложения пока что не выводятся.

Дело в том, что синтаксис цикла в AngularJS 2.0 немного отличается. Изменим часть кода, отвечающую за цикл в файле grid.html, как показано ниже:

<tr *ngFor="#employee of employees">

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

angular-update-01

Подводя итог

Компоненты являются важнейшей частью AngularJS 2.0 и в связи с этим, уместно будет сказать, что этот фреймворк отлично подходит для компонентного процесса создания приложения. С каждым последующим релизом в линейки версий 1.x, мы становимся ближе к тому способу построения приложения, который использует AngularJS 2.0.

В этой статье мы создали AngularJS директиву используя 1.4 версию фреймворка. Затем произвели рефакторинг полученной директивы, чтобы воспользоваться преимуществом компонентного подхода в версии 1.5. И наконец, мы обновили наш код до версии AngularJS 2.0.

Для более глубокого изучения процесса миграции AngularJS приложения, крайне полезным будет рассмотреть официальное руководство по этому вопросу. Также код AngularJS 2.0 может быть создан с помощью TypeScript и Dart, ознакомиться с которыми можно по приведенным ссылкам.

Исходный код приложения, созданного в этой статье, доступен по ссылке: https://github.com/sbogdanov108/angular_update

При создании статьи были использованы следующие источники:

  1. Upgrade Your App to Angular 1.5 Components and Beyond!
  2. Angular 2 Docs
  3. AngularJS API Docs
Поделиться

Оставить комментарий

Вы можете использовать следующие HTML-теги:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>
Обязательно к заполнению