Книга: Angular и TypeScript. Сайтостроение для профессионалов
Назад: 1. Знакомство с Angular
Дальше: 3. Навигация с помощью маршрутизатора Angular

2. Приступаем к работе с Angular

В этой главе:

написание вашего первого Angular-приложения;

• знакомство с универсальным загрузчиком модулей SystemJS;

• роль менеджеров пакетов;

• первая версия приложения для онлайн-аукциона.

В данной главе мы рассмотрим вопрос разработки Angular-приложений с помощью современных инструментов и веб-технологий, таких как аннотации, модули ES6 и загрузчики модулей. Angular изменяет привычный способ разработки приложений, написанных на JavaScript. Вы напишете три версии приложения Hello World. Кроме того, мы кратко рассмотрим менеджеры пакетов и универсальные загрузчики модулей SystemJS.

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

СОВЕТ

Если вы не знакомы с синтаксисом TypeScript и ECMAScript 6, то рекомендуем обратиться к приложениям А и Б, а затем продолжать чтение с данной главы.

2.1. Первое приложение для Angular

В этом разделе мы покажем три версии приложения Hello World, разработанные с помощью TypeScript, ES5 и ES6. Только здесь вы увидите Angular-приложения, написанные на ES5 и ES6, — все остальные фрагменты кода будут созданы с применением TypeScript.

2.1.1. Hello World с использованием TypeScript

Первое приложение выглядит довольно минималистично и поможет быстро начать работу с Angular. Оно состоит из двух файлов:

|──── index.html

└─── main.ts

Оба файла располагаются в каталоге hello-world-ts загружаемого кода для данной книги. Файл index.html — точка входа в приложение. Он содержит ссылки на фреймворк Angular, его зависимости, а также файл main.ts, включающий код для инициализации вашего приложения. Некоторые из этих ссылок могут находиться в конфигурационном файле загрузчика модулей (в рамках книги вы будете использовать загрузчики SystemJS и Webpack).

Загрузка ANGULAR в файле HTML

Код фреймворка Angular состоит из модулей (по одному файлу на модуль), которые объединяются в библиотеки, группируемые логически в пакеты наподобие @angular/core, @angular/common и пр. Ваше приложение должно загрузить требуемые пакеты до того, как будет загружен код приложения.

Теперь создадим файл index.html, работа которого будет начинаться с загрузки требуемых сценариев Angular, компилятора TypeScript и загрузчика модулей SystemJS.

В следующем фрагменте кода, приведенном в листинге 2.1, эти сценарии загружаются из сети распространения контента (content delivery network, CDN) /#/.

Листинг 2.1. Фрагмент содержимого файла TypeScript index.html

72622.png 

75986.png 

Когда приложение запущено, тег <hello-world> будет заменен содержимым шаблона из аннотации @Component, показанной в листинге 2.2.

СОВЕТ

Если вы используете Internet Explorer, то вам может понадобиться добавить дополнительный сценарий system-polyfills.js.

Сети распространения контента (Content delivery networks, CDNs)

unpkg (/#/) — сеть распространения контента для пакетов, опубликованных в реестре менеджера пакетов npm (/). Обратитесь к npmjs.com, чтобы найти последнюю версию необходимого вам пакета. Если хотите увидеть, какие еще версии этого пакета доступны, то запустите команду npm info packagename.

Сгенерированные файлы не отправляются в систему контроля версий, и Angular 2 не предоставляет готовых к использованию пакетов в своем репозитории Git. Они генерируются автоматически и публикуются вместе с пакетом npm (), в связи с чем вы можете применять unpkg так, чтобы непосредственно сослаться на готовые к выпуску пакеты из файлов HTML. Мы же предпочитаем задействовать версию Angular, установленную локально, а также ее зависимости, поэтому вы установите их с помощью npm в подразделе 2.4.2. Все, что было установлено с использованием npm, будет храниться в каталоге node_modules каждого проекта.

Файл TypeScript

Теперь создадим файл main.ts, который содержит код TypeScript/Angular и имеет три части.

1. Объявление компонента Hello World.

2. Оборачивание данного компонента в модуль.

3. Загрузка модуля.

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

Листинг 2.2. Содержимое файла TypeScript main.ts

72640.png 

76032.png 

Мы познакомимся с аннотациями @Component и @NgModule в разделе 2.2.

Что такое метаданные?

Как правило, метаданные — это пояснительная информация о данных. Например, в MP3-файле музыка является данными, а имя исполнителя, название песни и обложка альбома — метаданные. MP3-плеер включает в себя обработчик метаданных, который читает метаданные и отображает некую их часть во время воспроизведения песни.

Когда речь идет о классах, метаданные — дополнительная информация о классе. Например, декоратор @Component (также известный как «аннотация») указывает Angular (обработчику метаданных), что это не обычный класс, а компонент. Angular генерирует дополнительный код JavaScript, основываясь на информации, предоставленной в свойствах декоратора @Component.

Если же мы говорим о свойствах классов, декоратор @Input указывает Angular, что это свойство класса должно поддерживать привязку и иметь возможность получать данные из родительского компонента.

Кроме того, можно рассматривать декоратор как функцию, прикрепляющую некоторые данные к декорированному элементу. Декоратор @Component не изменяет декорированный класс, а лишь добавляет данные, описывающие его, чтобы компилятор Angular мог сгенерировать финальную версию кода компонента либо в памяти браузера (динамическая компиляция), либо в файле на диске (статическая компи­ляция).

Любой элемент приложения можно включить в файл HTML (или шаблон другого элемента) путем использования тега, который соответствует имени компонента в свойстве selector аннотации @Component. Селекторы компонентов похожи на селекторы CSS, поэтому, учитывая наличие селектора 'hello-world', вы отрисуете данный элемент на странице HTML с помощью элемента с именем <hello-world>. Angular преобразует эту строку в document.querySelectorAll(selector).

Обратите внимание на то, как в листинге 2.2 весь шаблон заключен в обратные кавычки для того, чтобы преобразовать его в строку. Таким образом, вы можете указывать одинарные и двойные кавычки внутри шаблона и разбивать его на несколько строк для более удачного форматирования. Шаблон содержит выражение для привязки данных {{ name }}, и во время выполнения Angular будет определять свойство name вашего компонента и заменять выражение привязки данных, располагающееся в фигурных скобках, конкретным значением.

Вы будете применять TypeScript для всех примеров кода, приведенных в этой книге, за исключением двух версий приложения Hello World, показанных далее. В одном из примеров будет приведена версия, написанная на ES5, а в другом — на ES6.

2.1.2. Hello World с помощью ES5

Для создания приложений на ES5 вы должны использовать особый пакет Angular, поставляющийся в формате Universal Module Definition (UMD, универсальное определение модуля) (обратите внимание на сочетание umd, присутствующее в URL). Этот пакет публикует все API Angular в глобальном объекте ng. Файл HTML приложения Hello World, написанного с помощью ES5, может выглядеть вот так (см. каталог hello-world-es5) (листинг 2.3).

Листинг 2.3. Содержимое файла ES5 index.html

<!DOCTYPE html>

<html>

<head>

  <script "></script>

  <script "></script>

  <script src="//unpkg.com/core-js/client/shim.min.js"></script>

  <script "></script>

  <script ">

     </script>

  <script src="//unpkg.com/@angular/[email protected]/bundles/

    compiler.umd.js"></script>

  <script src="//unpkg.com/@angular/[email protected]/bundles/

     platform-browser.umd.js"></script>

  <script src="//unpkg.com/@angular/[email protected]/

bundles/platform-browser-dynamic.umd.js"></script>

</head>

<body>

<hello-world></hello-world>

<script src="main.js"></script>

</body>

</html>

Поскольку ES5 не поддерживает аннотации и не имеет нативной системы модулей, файл main.js должен отличаться от версии для TypeScript (листинг 2.4).

Листинг 2.4. Содержимое файла ES5 main.js

// Компонент

(function(app) {

  app.HelloWorldComponent =

      ng.core.Component({

        selector: 'hello-world',

        template: '<h1>Hello {{name}}!</h1>'

      })

      .Class({

        constructor: function() {

          this.name = 'Angular 2';

        }

      });

})(window.app || (window.app = {}));

 

// Модуль

(function(app) {

  app.AppModule =

      ng.core.NgModule({

        imports: [ ng.platformBrowser.BrowserModule ],

        declarations: [ app.HelloWorldComponent ],

        bootstrap: [ app.HelloWorldComponent ]

      })

          .Class({

            constructor: function() {}

          });

})(window.app || (window.app = {}));

 

// Инициализация приложения

(function(app) {

  document.addEventListener('DOMContentLoaded', function() {

    ng.platformBrowserDynamic

        .platformBrowserDynamic()

        .bootstrapModule(app.AppModule);

  });

})(window.app || (window.app = {}));

Первая функция-выражение, вызываемая сразу после создания (immediately invoked function expression, IIFE), вызывает методы Component() и Class глобального пространства имен Angular ng.core. Вы определяете объект HelloWorldComponent, а метод Component прикрепляет метаданные, определяющие селектор и шаблон. Это позволяет превратить объект JavaScript в визуальный элемент.

Бизнес-логика компонента располагается в методе Class. В данном случае вы объявляете и инициализируете свойство name, которое будет привязано к шаблону компонента.

Вторая IIFE вызывает метод NgModule для создания модуля, который определяет объект HelloWorldComponent и указывает, что он является основным компонентом, присваивая его имя свойству bootstrap. Наконец, третья IIFE запускает приложение, вызывая метод bootstrapModule(), который загружает модуль, создает объект HelloWorldComponent и прикрепляет его к DOM браузера.

2.1.3. Hello World с помощью ES6

Версия ES6 приложения Hello World очень похожа на версию TypeScript, но в качестве компилятора SystemJS в этом случае используется Traceur. Основной файл index.html выглядит так (листинг 2.5).

Листинг 2.5. Содержимое файла ES6 index.html

72675.png 

Единственное различие между файлами main.js для ES6 и main.ts для TypeScript заключается в том, что во втором случае вам не нужно иметь заранее объявленный член класса name (листинг 2.6).

Листинг 2.6. Содержимое файла ES6 main.js

import {Component} from '@angular/core';

import { NgModule } from '@angular/core';

import { BrowserModule } from '@angular/platform-browser';

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

 

// Компонент

@Component({

  selector: 'hello-world',

  template: '<h1>Hello {{ name }}!</h1>'

})

class HelloWorldComponent {

  constructor() {

    this.name = 'Angular 2';

  }

}

 

// Модуль

@NgModule({

  imports: [ BrowserModule ],

  declarations: [ HelloWorldComponent ],

  bootstrap: [ HelloWorldComponent ]

})

export class AppModule { }

 

// Инициализация приложения

platformBrowserDynamic().bootstrapModule(AppModule);

2.1.4. Запуск приложений

Чтобы запустить любое веб-приложение, вам нужен простой сервер HTTP, например, или live-сервер. Последний позволяет автоматически перезагружать веб-страницы по мере модификации кода и сохранения файла запущенного приложения.

Для установки используйте следующую команду npm:

npm install -g

Для запуска сервера из командной строки в корневом каталоге проекта примените такую команду:

Нам нужно, чтобы сервер выполнял автоматические перезагрузки в браузере, поэтому установим и запустим live-сервер, используя аналогичную процедуру:

npm install live-server -g

live-server

Если вы задействуете , то понадобится вручную открыть браузер и ввести URL , а live-сервер откроет браузер за вас.

Чтобы запустить приложение Hello World, запустите live-сервер в корневом каталоге проекта; он загрузит в ваш браузер страницу index.html. Вы должны увидеть на странице надпись Hello Angular 2! (рис. 2.1). В браузере на панели Developer Tools (Инструменты разработчика) вы можете увидеть, что шаблон, указанный вами для HelloWorldComponent, становится содержимым элемента <hello-world>. При этом выражение привязки данных заменяется реальным значением, которое вы использовали для инициализации свойства name в конструкторе компонента.

ch02_01.tif 

Рис. 2.1. Запуск приложения Hello World

2.2. Элементы Angular-приложения

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

2.2.1. Модули

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

ОБРАТИТЕ ВНИМАНИЕ

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

С точки зрения синтаксиса модуль — это класс, аннотированный декоратором NgModule, который может содержать другие ресурсы. В разделе 2.1 вы уже использовали модуль, выглядевший вот так:

72699.png 

Импорт BrowserModule обязателен в корневом модуле, но, если ваше приложение будет иметь корневой модуль и модули с функциональностью, последние вместо него должны будут импортировать CommonModule. Члены всех импортированных модулей (например, FormsModule и RouterModule) доступны всем компонентам модуля.

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

platformBrowserDynamic().bootstrapModule(AppModule);

Модули вашего приложения могут быть загружены либо немедленно, как показано в предыдущем фрагменте кода, либо лениво (по мере необходимости) — это делает маршрутизатор (см. главу 3). Декоратор @NgModule встречается в каждой главе данной книги, так что вы сможете увидеть, как объявлять модули, имеющие несколько членов. Для получения более подробной информации о модулях Angular прочтите документацию на .

2.2.2. Компоненты

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

Любое Angular-приложение — это иерархия компонентов, упакованных в модули. Приложение должно иметь хотя бы один модуль и хотя бы один элемент, который называется корневым. Он ничем не отличается от других. Любой компонент, присвоенный свойству bootstrap вашего модуля, становится корневым.

Чтобы создать компонент, объявите класс и прикрепите к нему аннотацию @Component:

@Component({

  selector: 'app-component',

  template: '<h1>Hello !</h1>'

})

class HelloComponent {}

Каждая аннотация @Component должна задавать свойства selector и template (или templateUrl), которые позволяют установить, как компонент должен определяться и отрисовываться на странице.

Свойство selector похоже на селекторы CSS. Каждый элемент HTML, соответствующий селектору, отрисовывается как компонент Angular. Декоратор @Component можно рассматривать как функцию конфигурирования, которая дополняет класс. Если вы взглянете на скомпилированный код файла main.ts из листинга 2.2, то увидите, что компилятор Angular делает с декоратором @Component:

var core_1;

var HelloWorldComponent;

 

HelloWorldComponent = (function () {

      function HelloWorldComponent() {

          this.name = 'Angular 2';

      }

      HelloWorldComponent = __decorate([

          core_1.Component({

              selector: 'hello-world',

              template: '<h1>Hello {{ name }}!</h1>'

          }),

          __metadata('design:paramtypes', [])

      ], HelloWorldComponent);

      return HelloWorldComponent;

})

Каждый компонент должен определять представление, которое указывается либо в свойстве template, либо в свойстве templateUrl декоратора @Component:

@Component({

  selector: 'app-component',

  template: '<h1>App Component</h1>' })

class AppComponent {}

Для веб-приложений шаблон содержит разметку HTML. Можно также использовать другой язык разметки для отрисовки нативных мобильных приложений, предоставляемый сторонними фреймворками. Если разметка содержит несколько десятков линий (или меньше), то можно не выносить ее в отдельный файл с помощью свойства template. Мы не применяли обратные галочки в предыдущем примере, поскольку он включает всего одну строку разметки и не содержит одинарных и двойных кавычек. Более крупные фрагменты разметки HTML должны располагаться в отдельном файле HTML, на который ссылается свойство templateUrl.

Стиль компонентов задается путем обычного CSS. Вы можете использовать свойство styles для встроенных CSS и styleUrls, чтобы сослаться на внешний файл со стилями. Внешние файлы позволяют веб-дизайнерам работать со стилями, не изменяя код самого приложения. В конечном счете выбирать между HTML и CSS придется именно вам.

Представление можно рассматривать как результат объединения макета пользовательского интерфейса с данными. Фрагмент кода для AppComponent не имеет данных, которые можно объединить, но версия приложения Hello World, написанная с помощью TypeScript (см. файл main.ts в листинге 2.2), объединит разметку HTML со значением переменной name для того, чтобы создать представление.

примечание

В Angular отрисовка представления откреплена от компонентов, поэтому шаблон может представлять собой характерный для платформы нативный интерфейс наподобие NativeScript () или React Native ().

2.2.3. Директивы

Декоратор @Directive позволяет задать свой вариант поведения для элемента HTML (например, можно добавить возможность автозаполнения для элемента <input>). Каждый компонент, по сути, является директивой, с которой связано представление, но, в отличие от компонента, директива не имеет собственного представления.

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

72707.png 

Чтобы привязать события к обработчикам, заключите имя события в скобки. Когда в элементе host произойдет событие input, будет вызван обработчик onInput(), и объект события будет передан в этот метод как аргумент.

Рассмотрим пример того, как можно прикрепить директивы к элементу HTML:

<input type="text" log-directive/>

В следующем примере показана директива, которая изменяет цвет фона прикрепленного элемента на синий:

import { Directive, ElementRef, Renderer } from '@angular/core';

 

@Directive({ selector: '[highlight]' })

export class HighlightDirective {

  constructor(renderer: Renderer, el: ElementRef) {

    renderer.setElementStyle(el.nativeElement, 'backgroundColor', 'blue');

  }

}

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

Рассмотрим, как можно прикрепить директиву к элементу HTML <h1>:

<h1 highlight>Hello World</h1>

Все директивы, использованные в модуле, должны быть добавлены к свойству declaration декоратора @NgModule, как показано в этом примере:

@NgModule({

  imports: [ BrowserModule ],

  declarations: [ HelloWorldComponent,

                  HighlightDirective ],

  bootstrap: [ HelloWorldComponent ]

})

2.2.4. Краткое введение в привязку данных

Angular имеет механизм, который называется привязкой данных. Он позволяет синхронизировать свойства компонента и представление. Этот механизм довольно сложен, подробнее мы рассмотрим его в главе 5. Здесь же разберем наиболее распространенные формы синтаксиса привязки данных.

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

<h1>Hello {{ name }}!</h1>

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

<span [hidden]="isValid">The field is required</span>

Привязать обработчик события к событию элемента помогут круглые скобки:

<button (click)="placeBid()">Place Bid</button>

Если вы хотите сослаться на свойство объекта DOM внутри шаблона, то добавьте локальную переменную шаблона (ее имя должно начинаться с символа #), которая автоматически сохранит ссылку на соответствующий объект DOM, и примените точечную нотацию:

<input #title type="text" />

<span>{{ title.value }}</span>

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

2.3. Универсальный загрузчик модулей SystemJS

Большинство существующих веб-приложений загружают файлы JavaScript на страницу HTML с помощью тегов <script>. Несмотря на то, что можно добавить на страницу код Angular точно таким же способом, мы рекомендуем использовать библиотеку SystemJS. Angular тоже применяет ее для внутренних нужд.

В этом разделе мы кратко рассмотрим данную библиотеку, чтобы вы могли начать разрабатывать приложения на Angular. Для получения более подробного руководства по SystemJS обратитесь к странице библиотеки на GitHub: .

2.3.1. Обзор загрузчиков модулей

В финальной версии спецификации ES6 вводится понятие модулей, а также рассматриваются их синтаксис и семантика (). В первых набросках данной спецификации вводилось понятие глобального объекта System, ответственного за загрузку модулей в исполняемую среду независимо от того, чем она является — браузером или отдельным процессом. Однако от определения этого объекта избавились в финальной версии спецификации ES6. В данный момент он отслеживается группой Web Hypertext Application Technology Working Group (см. ). Объект System может стать частью спецификации ES8.

Загрузчик модулей — полифилл — из спецификации ES6 () предлагает начать использовать объект System прямо сейчас (не дожидаясь появления будущих спецификаций EcmaScript). Он стремится соответствовать стандартам будущего, но эта версия полифилла поддерживает только модули ES6.

Поскольку спецификация ES6 относительно нова, большая часть сторонних пакетов, размещенная в реестре NPM, еще не задействует модули ES6. В первых девяти главах данной книги мы применяем SystemJS, который не только включает в себя ES6 Module Loader, но и позволяет загружать модули, написанные в форматах AMD, CommonJS, UMD и других глобальных форматах. Поддержка этих форматов полностью прозрачна для пользователей SystemJS, поскольку загрузчик автоматически определяет формат модуля, использующий целевой сценарий. В главе 10 вы обратитесь к другому загрузчику модулей, называемому Webpack.

2.3.2. Загрузчики модулей против тегов <script>

Зачем вообще использовать загрузчики модулей, если можно просто проставить теги <script>?

У такого подхода есть несколько недостатков.

Разработчик отвечает за поддержку тегов <script> в файлах HTML. Некоторые из этих тегов могут стать избыточными с течением времени, но если вы забудете убрать их, то браузер все еще будет загружать их, увеличивая время загрузки и тратя впустую пропускную способность сети.

• Зачастую имеет значение порядок, в котором загружаются сценарии. Браузеры могут сохранить порядок загрузки сценариев, если только вы поместите теги <script> в разделе HTML-документа <head>. Однако такой подход считается признаком дурного тона, поскольку не дает отрисовывать страницу до того, как загрузятся все сценарии.

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

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

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

Указанные преимущества доступны не только для вашего кода, но и для сторонних пакетов (таких как Angular).

примечание

В книге мы используем термины «модуль» и «файл» попеременно. Модуль не может располагаться в нескольких файлах. Пакет обычно представлен одним файлом и содержит несколько модулей. Если в каком-то конкретном месте имеется разница между модулем и файлом, то мы явно укажем на это.

2.3.3. Приступаем к работе с SystemJS

Когда вы используете SystemJS на странице HTML, эта библиотека становится доступной в качестве глобального объекта System, который имеет несколько статических методов. В основном вы будете применять два метода: System.import() и System.config().

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

72716.png 

Если имя модуля начинается с символов ./, то оно является путем к файлу даже при отсутствии у имени расширения. SystemJS поначалу пытается соотнести имя модуля и сконфигурированное соотнесение, предоставленное либо как аргумент метода System.config(), либо в файле (наподобие systemjs.config.js).

Если имя соотнести нельзя, то оно считается путем к файлу.

примечание

В этой книге мы будем использовать как префикс ./, так и конфигурацию соотнесения , чтобы определить, какой файл будет загружен. Если вы увидите конструкцию System.import('app') и не сможете найти файл с именем app.ts, то взгляните на конфигурацию соотнесения вашего проекта.

Метод System.import() мгновенно возвращает объект Promise (см. приложение А). Когда разрешается промис для объекта модуля, после загрузки модуля вызывается метод обратного вызова then(). Если промис отклоняется, то ошибки обрабатываются в методе catch().

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

// lib.js

export let foo = 'foo';

// main.js

System.import('./lib.js').then(libModule => {

  libModule.foo === 'foo'; // true

});

Здесь вы используете метод then(), чтобы указать функцию обратного вызова, которая будет вызвана при загрузке lib.js. Загруженный объект передается как аргумент выражения со стрелкой.

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

А как насчет операторов импорта ES6? В вашем первом Angular-приложении вы использовали вызов System.import() в файле index.html для загрузки корневого модуля приложения, main.ts. В свою очередь, сценарий main.ts импортирует модули Angular с помощью собственного оператора import.

Когда SystemJS загружает main.ts, он автоматически компилирует его в код, совместимый с ES5, поэтому в коде, исполняемом браузером, вы не встретите операторов импорта. В будущем, когда модули ES6 будут нативно поддерживаться основными браузерами, данный шаг перестанет быть обязательным, и операторы импорта будут работать аналогично System.import() за исключением того, что не смогут контролировать момент, когда загружается модуль.

примечание

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

Пример приложения

Рассмотрим приложение, которое должно загружать сценарии ES5 и ES6. Это приложение будет содержать три файла (см. каталог systemjs-demo):

│──── index.html

│──── ES6module.js

└─── es5module.js

В обычном веб-приложении файл index.html будет содержать теги <script>, ссылающиеся на файлы ES6module.js и es5module.js. Каждый из этих файлов в таком случае был бы автоматически загружен и запущен в браузере. Но данный подход имеет несколько недостатков, которые мы рассмотрели в подразделе 2.3.2. Изучим способы решения этих проблем с помощью SystemJS в рамках приложения-примера.

Вы используете оператор экспорта ES6, чтобы сделать имя модуля ES6module.js доступным за пределами сценария. Наличие оператора экспорта автоматически сделает файл модулем ES6:

export let name = 'ES6';

console.log('ES6 module is loaded');

Файл es5module.js не содержит синтаксиса ES6 и применяет формат модуля CommonJS для экспортирования имени модуля. По сути, вы прикрепляете к объекту exports переменные, которые хотели бы увидеть за пределами модуля:

exports.name = 'ES5';

console.log('ES5 module is loaded');

В показанном далее файле index.html (листинг 2.7) выполняется незаметный импорт модулей CommonJS и ES6 с помощью SystemJS.

Листинг 2.7. Файл index.html и SystemJS

72723.png 

Поскольку вызов System.import() возвращает объект Promise, вы можете начать загружать по несколько модулей за раз и выполнять другой код, когда все модули загружены.

После запуска приложения вы увидите в консоли разработчика следующий результат (держите открытой панель Developer Tools (Инструменты разработчика), чтобы его увидеть):

Live reload enabled.

ES6 module is loaded

ES5 module is loaded

The following modules are loaded: ES6, ES5

Первая строка приходит от live-сервера, а не от приложения. Как только будет загружен один из модулей, вы увидите сообщение в журнале. После загрузки всех модулей будет выполнена функция обратного вызова, и вы увидите последнее сообщение журнала.

Конфигурирование систем

До сих пор вы использовали стандартную конфигурацию SystemJS, но можно сконфигурировать практически любой аспект его работы, задействуя метод System.config(), принимающий в качестве аргумента объект конфигурации. Данный метод может быть вызван несколько раз с передачей ему разных объектов конфигурации. Если значение одного и того же параметра устанавливается несколько раз, то будет применено последнее значение. Вы можете либо встроить сценарий с помощью System.config() в файл HTML, используя тег <script> (см. раздел 2.1), либо сохранить код для System.config() в отдельном файле (таком как systemjs.config.js) и включить его в файл HTML, применяя тег <script>.

Полный список параметров конфигурации SystemJS можно найти на GitHub (). Мы лишь кратко обсудим некоторые варианты конфигурации, использованные в данной книге.

BASEURL

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

System.config({ baseURL: '/app' });

System.import('es6module.js');   // GET /app/es6module.js

System.import('./es6module.js'); // GET /es6module.js

System.import(''); // GET /

  ES6module.js

DEFAULTJSEXTENSIONS

Если defaultJSExtensions имеет значение true, то расширение .js будет автоматически добавлено к пути файла. При наличии у имени модуля расширения, отличного от .js, это расширение будет добавлено в любом случае:

System.config({ defaultJSExtensions: true });

System.import('./es6module');    // GET /es6module.js

System.import('./es6module.js'); // GET /es6module.js

System.import('./es6module.ts'); // GET /es6module.ts.js

ПРИМЕЧАНИЕ

Свойство defaultJSExtensions обеспечивает лишь обратную совместимость, оно будет удалено в будущих версиях SystemJS.

MAP

Этот параметр позволяет создавать псевдоним для имени модуля. Когда вы импортируете модуль, его имя будет заменено связанным значением, если только оригинальное имя не представляет какой-либо путь (абсолютный или относительный). Параметр map применяется перед baseURL:

System.config({ map: { 'es6module.js': 'esSixModule.js' } });

System.import('es6module.js');   // GET /esSixModule.js

System.import('./es6module.js'); // GET /es6Module.js

Рассмотрим еще один пример использования параметра map:

System.config({

  baseURL: '/app',

  map: { 'es6module': 'esSixModule.js' }

});

System.import('es6module'); // GET /app/esSixModule.js

PACKAGES

Этот параметр предоставляет удобный способ задать метаданные и соотнести конфигурацию, которая характерна для заданного пути. Например, в следующем фрагменте кода для SystemJS указывается, что вызов System.import('app') должен загружать модуль, расположенный в файле main_router_sample.ts, путем предоставления лишь имени файла и стандартного расширения для TypeScript — ts:

System.config({

  packages: {

    app: {

      defaultExtension: "ts",

      main: "main_router_sample"

    }

  }

});

System.import('app');

PATHS

Этот параметр похож на map, но поддерживает подстановочные символы. Применяется после map, но перед baseURL (см. листинг 2.6). Вы можете использовать оба этих параметра, но помните, что paths является частью спецификации Loader (см. ) и реализации загрузчика модулей ES6 (см. ), а map распознается только SystemJS:

System.config({

  baseURL: '/app',

  map: { 'es6module': 'esSixModule.js' },

  paths: { '*': 'lib/*' }

});

System.import('es6module'); // GET /app/lib/esSixModule.js

Во многих примерах данной книги вы найдете вызов System.import('app'), открывающий файл с другим именем (которое отличается от app), поскольку было сконфигурировано свойство map или packages. Когда вы видите конструкцию наподобие import {Component} from '@angular/ core';, @angular ссылается на имя, соотнесенное с реальным каталогом, где располагается фреймворк Angular. Элемент core является подкаталогом, основной файл в нем указан в конфигурации SystemJS, как продемонстрировано в этом примере:

packages: {

        '@angular/core' : {main: 'index.js'}

}

TRANSPILER

Данный параметр позволяет указать имя модуля компилятора, который будет использован при загрузке модулей приложения. Если файл не содержит хотя бы один оператор импорта или экспорта, то не будет скомпилирован. Параметр transpiler может иметь одно из следующих значений: typescript, traceur или babel:

System.config({

  transpiler: 'traceur',

  map: {

    traceur: '//'

  }

});

TYPESCRIPTOPTIONS

Этот параметр позволяет настраивать параметры компилятора TypeScript.

Список всех доступных параметров можно найти в документации к TypeScript: .

2.4. Выбираем менеджер пакетов

Скорее всего, при написании веб-приложения вы будете использовать сторонние библиотеки. В примерах кода, приведенных в нашей книге, тоже применяются несколько сторонних библиотек. Вы будете задействовать фреймворк Angular в большинстве примеров кода данной книги, а для приложения-аукциона вы также обратитесь к библиотеке Bootstrap от Twitter, зависимостью которой является jQuery. Ваше приложение может требовать наличия определенных версий этих зависимостей.

Загрузкой библиотек, фреймворков и их зависимостей управляет менеджер пакетов, и вам нужно решить, на каком из наиболее популярных вариантов остановиться. Разработчики, использующие JavaScript, могут быть ошеломлемы разнообразием доступных менеджеров пакетов: npm, Bower, jspm, Jam и Duo — и это лишь немногие из них.

Типичный проект включает в себя конфигурационный файл, содержащий имена и версии требуемых библиотек и фреймворков. Рассмотрим фрагмент конфигурации package.json npm, которую вы будете использовать для приложения-аукциона:

"scripts": {

    "start": "live-server"

  },

  "dependencies": {

    "@angular/common": "2.0.0",

    "@angular/compiler": "2.0.0",

    "@angular/core": "2.0.0",

    "@angular/forms": "2.0.0",

    "@angular/": "2.0.0",

    "@angular/platform-browser": "2.0.0",

    "@angular/platform-browser-dynamic": "2.0.0",

    "@angular/router": "3.0.0",

 

    "core-js": "^2.4.0",

    "rxjs": "5.0.0-beta.12",

    "systemjs": "0.19.37",

    "zone.js": "0.6.21",

    "bootstrap": "^3.3.6",

    "jquery": "^2.2.2"

    },

    "devDependencies": {

    "live-server": "0.8.2",

    "typescript": "^2.0.0"

  }

В разделе scripts указывается, какие команды нужно запустить, если вы введете в командной строке npm start. В этом случае следует запустить live-сервер. В разделе dependencies перечислены все сторонние библиотеки и инструменты, необходимые для работы среды выполнения, в которой будет развернуто приложение.

В разделе devDependencies указываются инструменты, которые должны присутствовать на вашем компьютере. Например, вы не будете использовать live-сервер для коммерческой версии, поскольку этот сервер слишком простой и удовлетворяет лишь нужды разработчиков. В приведенной выше конфигурации также указывается: компилятор TypeScript нужен только во время разработки; вы можете догадаться, что весь код TypeScript будет скомпилирован в код JavaScript.

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

В начале работы с Angular мы знали, что будем применять загрузчик модулей SystemJS. Позже нам стало известно, что автор SystemJS (Гай Бедфорд, Guy Bedford) также создал менеджер пакетов jspm, задействующий данный загрузчик, поэтому мы решили использовать jspm. Какое-то время мы применяли npm для установки инструментов, а jspm — для установки зависимостей. Такой подход работал хорошо, но при использовании jspm браузер выполнял 400+ запросов к серверу для демонстрации первой страницы довольно простого приложения. Нам показалось, что 3,5 секунды, за которые оно открывается, — это довольно много.

Мы решили попробовать применять npm для управления зависимостями во время разработки. Получилось гораздо лучше — всего 30 запросов к серверу, приложение запускалось всего за 1,5 секунды.

Мы кратко рассмотрим оба менеджера пакетов и покажем, как запустить новый проект с помощью каждого из них. Менеджер jspm еще довольно молод и со временем может улучшиться, но для наших проектов мы решили использовать npm.

2.4.1. Сравниваем npm и jspm

Менеджер пакетов npm изначально создавался для управления модулями Node.js, написанными в формате CommonJS. Этот формат не был разработан для веб-приложений, поскольку предполагалось, что модули загружаются синхронно. Рассмотрим следующий фрагмент кода:

var x = require('module1');

var y = require('module2');

var z = require('module3');

Загрузка модуля module2 не начнется, пока не будет загружен модуль module1, а модуль module3 запустится только после загрузки module2. Это приемлемо для приложений для ПК, написанных с помощью Node.js, поскольку загрузка выполняется с локального компьютера, но подобный синхронный процесс замедлит загрузку приложений.

Еще одним слабым местом npm было использование вложенных зависимостей. Если пакеты A и B зависели от пакета C, то каждый из них хранил копию C в своем каталоге, поскольку A и B могли зависеть от разных версий C. Несмотря на то, что это было приемлемо для приложений, написанных на Node.js, такой подход не годился для приложений, загружающихся в браузер. Даже загрузка одной и той же версии библиотеки дважды может вызвать проблемы. Если же загружены две разные версии, то шанс возникновения сбоя в приложении становится еще выше.

С вложенными зависимостями разобрались в версии npm 3, но проблема была решена лишь частично. По умолчанию, npm пытается установить пакет C в тот же каталог, где находятся A и B, поэтому A и B пользуются одной копией C. Но если A и B требуют наличия конфликтующих версий C, то npm возвращается к подходу с вложенными зависимостями. Библиотеки, созданные для приложений, работающих на стороне клиента, обычно включают встроенные версии (пакеты, состоящие из одного файла) в свои пакеты npm. Пакеты не содержат сторонних зависимостей, в связи с чем нужно вручную загружать их на странице. Это помогает избежать проблемы с вложенными зависимостями.

Менеджер пакетов jspm создан для модулей и загрузчиков модулей ES6. Он не размещает пакеты сам по себе, а предлагает концепцию реестров, которая позволяет создавать собственные каталоги для пакетов. Сразу после установки jspm дает возможность устанавливать пакеты как из реестра npm, так и непосредственно из репозиториев GitHub.

Менеджер пакетов jspm был разработан для совместной работы с SystemJS. Когда вы инициализируете новый проект или устанавливаете пакет с помощью jspm, он автоматически создает конфигурацию для SystemJS для загрузки модулей. В отличие от npm он использует подход плоских зависимостей, так что в проекте имеется только одна копия каждой библиотеки. Это позволяет применять операторы импорта даже для загрузки стороннего кода. Таким образом решается проблема порядка загрузки сценариев и гарантируется, что приложение загружает только те модули, которые будет задействовать на самом деле.

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

Еще одно слабое место jspm — вы не можете использовать пакеты npm или репозиторий GitHub как пакет jspm сразу после установки. Для этого может потребоваться дополнительное конфигурирование, чтобы jspm мог соответствующим образом настроить SystemJS так, чтобы модули загружались из пакета.

На момент написания данной книги в реестре jspm имеется меньше 500 пакетов, готовых к работе с SystemJS, по сравнению с 250 000 пакетами для npm.

2.4.2. Создаем проект Angular с помощью npm

Чтобы начать новый проект, управляемый npm, создайте новый каталог (например, angular-seed) и откройте его в командном окне. Затем запустите команду npm init -y, которая создаст исходную версию конфигурационного файла package.json. Как правило, при запуске команды npm init вам будет задано несколько вопросов при создании файла, но флаг -y позволяет принять значения по умолчанию для всех параметров. В следующем примере показана эта команда, запущенная в пустом каталоге angular-seed.

$ npm init -y

Wrote to /Users/username/angular-seed/package.json:

{

  "name": "angular-seed",

  "version": "1.0.0",

  "description": "",

  "main": "index.js",

  "scripts": {

    "test": "echo \"Error: no test specified\" && exit 1"

  },

  "keywords": [],

  "author": "",

  "license": "ISC"

}

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

Поскольку вы не будете публиковать пакет в реестре npm, следует удалить все свойства, кроме name, description и scripts. Добавьте также свойство "private": true, поскольку оно не создается по умолчанию. Это гарантирует, что пакет не будет опубликован в реестре npm. Файл package.json должен выглядеть так (листинг 2.8).

Листинг 2.8. Содержимое файла package.json

{

  "name": "angular-seed",

  "description": "An initial npm-managed project for Chapter 2",

  "private": true,

    "scripts": {

  "test": "echo \"Error: no test specified\" && exit 1"

  }

}

Конфигурация scripts позволяет указать команды, доступные для запуска в командном окне. По умолчанию npm init создает тестовую команду, которую можно запустить с помощью следующего вызова: npm test. Заменим ее командой start; ее вы будете использовать для запуска live-сервер, установленного в подразделе 2.4.1. Рассмотрим конфигурацию свойства scripts:

{

  ...

  "scripts": {

    "start": "live-server"

  }

}

Можно запустить любую команду npm из раздела scripts с помощью синтаксиса npm run mycommand, например, так: npm run start. Кроме того, можно использовать сокращение npm start вместо npm run start. Такой синтаксис доступен только для заранее определенных сценариев npm (см. документацию к npm на ).

Теперь нужно, чтобы npm загрузил в этот проект Angular в качестве зависимости. В версии приложения Hello World, написанной с помощью TypeScript, вы применяли код Angular, который размещен на сервере unpkg CDN, но в нашем случае нужно загрузить его в каталог проекта. Вам понадобятся также локальные версии SystemJS, live-сервер и компилятор TypeScript.

Пакеты npm зачастую состоят из других пакетов, оптимизированных для коммерческого применения; они не включают исходный код библиотек. Добавьте в файл package.json раздел, который использует исходный код (но не оптимизированные пакеты) заданных пакетов. Этот раздел вставляется сразу после строки с лицензиями (следует обновить версии зависимостей до самых свежих) (листинг 2.9).

Листинг 2.9. Использование исходного кода пакетов

"dependencies": {

  "@angular/common": "2.0.0",

  "@angular/compiler": "2.0.0",

  "@angular/core": "2.0.0",

  "@angular/forms": "2.0.0",

  "@angular/": "2.0.0",

  "@angular/platform-browser": "2.0.0",

  "@angular/platform-browser-dynamic": "2.0.0",

  "@angular/router": "3.0.0",

 

  "core-js": "^2.4.0",

  "rxjs": "5.0.0-beta.12",

  "systemjs": "0.19.37",

  "zone.js": "0.6.21"

},

"devDependencies": {

  "live-server": "0.8.2",

  "typescript": "^2.0.0"

}

Теперь запустите команду npm install в командной строке из каталога, где находится ваш файл package.json, и npm начнет загружать предыдущие пакеты и их зависимости в каталог node_modules. По завершении этого процесса вы увидите десятки подкаталогов в каталоге node_modules, включая @angular, systemjs, live-server и typescript:

angular-seed

│─── index.html

│─── package.json

└─── app

│    └─── app.ts

│─── node_modules

│    │─── @angular

│    │─── systemjs

│    │─── typescript

│    │─── live-server

│    └─── ...

Создадим несколько измененную версию файла index.html со следующим содержимым в каталоге angular-seed (листинг 2.10).

Листинг 2.10. Обновленный файл index.html

<!DOCTYPE html>

<html>

<head>

  <title>Angular seed project</title>

  <meta charset="UTF-8">

  <meta name="viewport" content="width=device-width, initial-scale=1">

 

  <script src="node_modules/typescript/lib/typescript.js"></script>

  <script src="node_modules/core-js/client/shim.min.js"></script>

  <script src="node_modules/zone.js/dist/zone.js"></script>

  <script src="node_modules/systemjs/dist/system.src.js"></script>

  <script src="systemjs.config.js"></script>

  <script>

    System.import('app').catch(function(err){ console.error(err); });

  </script>

</head>

 

<body>

<app>Loading...</app>

</body>

</html>

Обратите внимание на то, что теги script теперь загружают требуемые зависимости из локального каталога node_modules. То же верно и для конфигурационного файла SystemJS systemjs.config.js, показанного далее (листинг 2.11).

Листинг 2.11. Содержимое файла systemjs.config.js

System.config({

    transpiler: 'typescript',

    typescriptOptions: {emitDecoratorMetadata: true},

    map: {

      '@angular': 'node_modules/@angular',

      'rxjs' : 'node_modules/rxjs'

    },

    paths: {

      'node_modules/@angular/*': 'node_modules/@angular/*/bundles'

    },

    meta: {

      '@angular/*': {'format': 'cjs'}

    },

    packages: {

      'app'                              : {main: 'main', defaultExtension:

        'ts'},

      'rxjs'                             : {main: 'Rx'},

      '@angular/core'                    : {main: 'core.umd.min.js'},

      '@angular/common'                  : {main: 'common.umd.min.js'},

      '@angular/compiler'                : {main: 'compiler.umd.min.js'},

      '@angular/platform-browser'        : {main: 'platform-

        browser.umd.min.js'},

      '@angular/platform-browser-dynamic': {main: 'platform-browser-

        dynamic.umd.min.js'}

    }

});

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

Каждый пакет Angular поставляется с каталогом bundles, который содержит сжатый код. В разделе packages конфигурационного файла SystemJS вы соотносите имя app с основным сценарием, расположенным в файле main.ts, после чего при вызове команды System.import(app) в файле index.html будет загружаться файл main.ts.

Добавьте еще один конфигурационный файл в корневой каталог проекта, где вы указываете параметры компилятора tsc (листинг 2.12).

Листинг 2.12. Содержимое файла tsconfig.json

{

  "compilerOptions": {

    "target": "ES5",

    "module": "commonjs",

    "experimentalDecorators": true,

    "noImplicitAny": true

  }

}

Если вы не знакомы с TypeScript, то обратитесь к приложению Б, в котором объясняется, что для запуска кода TypeScript его сначала нужно скомпилировать в JavaScript с помощью компилятора tsc.

Фрагменты кода, приведенные в главах 1–7, работают и без явного запуска этого компилятора, поскольку SystemJS сам по себе использует tsc для динамической компиляции TypeScript в JavaScript по мере загрузки файла сценария. Однако вам следует хранить файл tsconfig.json в корневом каталоге проекта, поскольку он нужен для некоторых IDE.

примечание

Если код Angular динамически компилируется в браузере (не путать с компиляцией в код на JavaScript), то это называется динамической компиляцией (just-in-time (JIT) compilation). Процесс, когда код скомпилирован заранее и имеет особый заголовок ngc, называется компиляцией перед выполнением (ahead-of-time (AoT) compilation). В данной главе мы опишем приложение, скомпилированное методом JIT.

Код приложения будет находиться в трех файлах:

app.component.ts — единственный компонент вашего приложения;

• app.module.ts — объявление модуля, который будет содержать ваш компонент;

• main.ts — инициализатор модуля.

В подразделе 2.3.3 вы соотнесли имя с файлом main.ts, поэтому создадим каталог app, включающий файл app.component.ts, который имеет следующее содержимое (листинг 2.13).

Листинг 2.13. Содержимое файла app.component.ts

import {Component} from '@angular/core';

 

@Component({

    selector: 'app',

    template: `<h1>Hello {{ name }}!</h1>`

})

export class AppComponent {

    name: string;

    constructor() {

        this.name = 'Angular 2';

    }

}

Теперь нужно создать модуль, который будет содержать компонент AppComponent. Поместите этот код в файлapp.module.ts (листинг 2.14).

Листинг 2.14. Содержимое файла app.module.ts

import { NgModule }      from '@angular/core';

import { BrowserModule } from '@angular/platform-browser';

import { AppComponent }  from './app.component';

 

@NgModule({

    imports:      [ BrowserModule ],

    declarations: [ AppComponent ],

    bootstrap:    [ AppComponent ]

})

export class AppModule { }

Этот файл включает лишь определение модуля Angular. Класс имеет аннотацию @NgModule, содержащую модуль BrowserModule, который должны импортировать все браузеры.

Поскольку в модуле только один класс, нужно указать его в свойстве declarations и отметить его как класс bootstrap:

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app.module';

platformBrowserDynamic().bootstrapModule(AppModule);

Запустите приложение, выполнив команду npm start из каталога angular-seed. Откроется браузер, и вы на долю секунды увидите сообщение Loading…, за которым последует сообщение Hello Angular 2!. На рис. 2.2 показано, как данное приложение выглядит в браузере Chrome: во вкладке Network (Сеть) открыта панель Developer Tools (Инструменты разработчика) — это позволяет видеть фрагмент кода, загруженный браузером, а также понадобившееся время.

ch02_02.tif 

Рис. 2.2. Запуск приложения из проекта, управляемого npm

Не пугайтесь размера загруженного файла, вы оптимизируете его в главе 10. Вы используете live-сервер, поэтому, как только измените и сохраните код приложения, он перезагрузит страницу в соответствии с последней версией кода. Теперь применим полученные вами знания для создания приложения, более сложного, чем Hello World.

2.5. Практикум: приступаем к работе над онлайн-аукционом

С этого момента каждая глава будет заканчиваться разделом «Практикум», в котором содержатся инструкции по разработке определенного аспекта приложения-аукциона. С помощью данного приложения пользователи могут увидеть список рекламируемых товаров, просмотреть более подробную информацию о требуемом продукте, найти продукт и отслеживать ставки других пользователей. Вы будете постепенно добавлять код в это приложение для того, чтобы закрепить знания, полученные в каждой главе. Исходный код, который поставляется с книгой, включает в себя полную версию раздела «Практикум» для каждой главы (она находится в каталоге auction), но мы просим вас попробовать написать код самостоятельно.

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

ch02_03.tif 

Рис. 2.3. Главная страница приложения онлайн-аукциона

Вы будете использовать серые прямоугольники, предоставленные удобным сервисом Placeholder.com (/), генерирующим заполнители нужного размера. Чтобы увидеть сгенерированные изображения, вам нужно подключиться к Интернету во время работы приложения. В следующих разделах содержатся инструкции, которые помогут выполнить упражнение.

примечание

Если вы хотите только прочитать код рабочей версии онлайн-аукциона, то обратитесь к коду, находящемуся в каталоге auction, который поставляется вместе с этой главой. Для запуска предоставленного кода переключитесь в каталог auction, запустите команду npm install, чтобы установить все необходимые зависимости в каталог node_modules, и запустите приложение с помощью команды npm start.

2.5.1. Первичная настройка проекта

Для настройки проекта сначала скопируйте содержимое каталога angular-seed в отдельное место и назовите его auction. Далее измените поля name и description в файле package.json. Откройте командное окно и переключитесь на только что созданный каталог auction. Запустите команду npm install; это создаст каталог node_modules с зависимостями, указанными в файле package_json.

В данном проекте вы будете использовать Twitter Bootstrap в качестве фреймворка CSS, а также библиотеку для создания пользовательского интерфейса, содержащую адаптивные компоненты. Адаптивный веб-дизайн — подход, позволяющий создавать сайты, которые изменяют макет на основе ширины области просмотра устройства пользователя. Термин адаптивный компонент означает, что макет элемента может адаптироваться в зависимости от размера экрана. Поскольку библиотека Bootstrap создана на основе jQuery, вам нужно запустить следующие команды для ее установки:

72761.png 

СОВЕТ

Мы рекомендуем использовать такие IDE, как WebStorm или Visual Studio Code. Большую часть шагов, требуемых для завершения этого упражнения, можно выполнить с помощью IDE. WebStorm даже позволяет открывать окно терминала внутри IDE.

Теперь создайте файл systemjs.config.js, чтобы сохранить конфигурацию SystemJS (листинг 2.15). Вы включите его в тег <script> в файле index.html.

Листинг 2.15. Содержимое файла systemjs.config.js

72778.png 

Файл systemjs.config.js должен быть включен в index.html. Эта конфигурация пакета app позволяет использовать строку <script>System.import ('app')</script> в файле index.html, что загрузит содержимое файла app/main.ts.

На этом мы заканчиваем конфигурировать среду разработки для проекта аукциона. Теперь вы готовы писать код приложения.

примечание

На момент написания данной книги команда разработчиков Angular создает компоненты Angular Material (см. ). Вы можете использовать их вместо Twitter Bootstrap, когда они будут готовы.

2.5.2. Разработка главной страницы

В данном упражнении вы создадите главную страницу; она будет разбита на несколько компонентов Angular. Это упростит поддержку кода и позволит повторно использовать элементы других представлений. На рис. 2.4 вы увидите главную страницу, на которой выделены все компоненты. Вам нужно создать каталоги для хранения всех элементов и сервисов приложения, показанных далее:

app

└─── components

     │─── application

     │─── carousel

     │─── footer

     │─── navbar

     │─── product-item

     │─── search

     │─── stars

└─── services

80332.png 

Рис. 2.4. Главная страница онлайн-аукциона, ее компоненты выделены

Каждый каталог внутри каталога components содержит код соответствующего компонента. Это позволяет хранить вместе все файлы, связанные с одним элементом. Большинство компонентов состоит из двух файлов: HTML и TypeScript. Но иногда может понадобиться добавить файл CSS, в котором будут содержаться стили компонента. Каталог services будет включать файл с классами, поставляющий данные приложению.

Первая версия главной страницы состоит из семи компонентов. В данном упражнении мы рассмотрим (а вы создадите) три самых интересных элемента, расположенных в каталогах application, product-item и stars. Это позволит попрактиковаться в написании кода, а по завершении упражнения вы можете скопировать остальные компоненты в каталог с вашим проектом.

примечание

В разделе «Практикум» главы 3 вы проведете рефакторинг кода, чтобы интегрировать карусель и продукты в компонент HomeComponent.

Точкой входа в приложение является файл index.html. Вы скопировали его из каталога angular-seed, и теперь нужно его модифицировать (листинг 2.16). Этот файл довольно мал и увеличится в размерах лишь незначительно, поскольку большая часть ваших зависимостей будет загружена с помощью SystemJS, а весь пользовательский интерфейс представлен одним корневым компонентом Angular, который будет самостоятельно использовать элементы-потомки.

Листинг 2.16. Обновленный файл index.html

72797.png 

Содержимое файла main.ts из каталога app останется таким же, как и в проекте angular-seed:

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app.module';

platformBrowserDynamic().bootstrapModule(AppModule);

Обновите файл app.module.ts, объявив все компоненты и сервисы, которые будете использовать в приложении (листинг 2.17).

Листинг 2.17. Обновленный файл app.module.ts

72808.png 

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

Компонент application

Является корневым компонентом аукциона, таковым он объявлен в AppModule. Он служит узлом для всех остальных элементов. Его исходный код состоит из трех файлов: application.ts, application.html и application.css. Мы предполагаем, что вы знакомы с основами CSS, поэтому не будем рассматривать здесь соответствующий файл, а уделим внимание лишь первым двум.

Создайте компонент ApplicationComponent и сохраните его в файле applica­tion.ts, который располагается в каталоге app/components/application. Его содержимое показано ниже (листинг 2.18).

Листинг 2.18. Содержимое файла application.ts

72817.png 

Простое объявление аргументов конструктора и их типа даст команду Angular создать и внедрить этот объект (ProductService). Внедряемые объекты нужно сконфигурировать с помощью поставщиков, вы уже объявили один поставщик в модуле AppModule ранее. Префикс private превратит productService в переменную — член класса, и вы сможете получить к ней доступ, используя конструкцию this.productService.

примечание

В листинге 2.18 используется стратегия инкапсуляции представлений ViewEn­cap­sulation.None, чтобы применять стили из файла application.css не только для компонента ApplicationComponent, но и для всего приложения. Мы рассмотрим различные стратегии инкапсуляции в главе 6.

Создайте файл application.html со следующим содержимым (листинг 2.19).

Листинг 2.19. Содержимое файла application.html

<auction-navbar></auction-navbar>

<div class="container">

  <div class="row">

    <div class="col-md-3">

      <auction-search></auction-search>

    </div>

    <div class="col-md-9">

      <div class="row carousel-holder">

        <div class="col-md-12">

          <auction-carousel></auction-carousel>

        </div>

      </div>

      <div class="row">

        <div *ngFor="let prod of products" class="col-sm-4 col-lg-4 col-md-4">

          <auction-product-item [product]="prod"></auction-product-item>

        </div>

      </div>

    </div>

  </div>

</div>

<auction-footer></auction-footer>

Вы будете использовать несколько собственных элементов HTML, которые представляют ваши компоненты: <auction-navbar>, <auction-search>, <auction-carousel>, <auction-product-item> и <auction-footer>. Вы добавите их в файл index.html точно так же, как и <auction-application>.

Самая интересная часть этого файла — способ отображения списка продуктов. Каждый из них будет представлен одинаковым фрагментом HTML на веб-странице. Поскольку у вас есть несколько продуктов, нужно отрисовать один и тот же фрагмент HTML несколько раз. Директива NgFor используется внутри компонента для прохождения в цикле по списку элементов коллекции данных и отрисовки разметки HTML для каждого элемента. Вы можете применять укороченную версию синтаксиса *ngFor, чтобы представить директиву NgFor.

<div *ngFor="let prod of products" class="col-sm-4 col-lg-4 col-md-4">

  <auction-product-item [product]="prod"></auction-product-item>

</div>

Поскольку *ngFor находится внутри тега <div>, каждая итерация цикла отрисует <div> с содержимым соответствующего элемента <auction-product-item>. Чтобы передать объект продукта в компонент ProductComponent, используйте квадратные скобки для реализации привязки свойств: [product]="prod". Элемент [product] ссылается на соответствующий продукт, расположенный внутри компонента, представленного <auction-product-item>, а prod — это локальная переменная шаблона, динамически объявленная в директиве *ngFor с помощью конструкции let prod. Мы рассмотрим привязку свойств более подробно в главе 5.

Стили col-sm-4, col-lg-4 и col-md-4 мы получаем из библиотеки Bootstrap от Twitter, где ширина окна поделена на 12 невидимых колонок. В нашем примере вы должны выделить 4 колонки (треть ширины элемента <div>), если устройство имеет маленький (sm (small) — 768 пикселов или больше), большой (lg (large) — 1200 пикселов и больше) или средний (md (medium) — 992 пиксела или больше) размер экрана.

Поскольку мы не указали количество колонок для очень маленьких устройств (xs —768 пикселов или меньше), вся ширина элемента <div> будет выделена одному элементу <auction-product>.

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

примечание

Компонент ApplicationComponent полагается на наличие других элементов (например, ProductItemComponent), которые вы создадите на следующих шагах. Если попробуете запустить аукцион сейчас, то увидите ошибки в консоли разработчика вашего браузера.

Компонент product item

В каталоге product-item создайте файл product-item.ts, в котором объявляется компонент ProductItemComponent, представляющий один объект продукта аукциона (листинг 2.20). Исходный код файла product-item.ts структурирован примерно так же, как и код файла application.ts: вначале идут операторы импорта, а затем объявление класса компонента, аннотированное декоратором @Component.

Листинг 2.20. Содержимое файла product-item.ts

import {Component, Input} from '@angular/core';

import StarsComponent from 'app/components/stars/stars';

import {Product} from 'app/services/product-service';

 

@Component({

  selector: 'auction-product-item',

  templateUrl: 'app/components/product-item/product-item.html'

})

export default class ProductItemComponent {

  @Input() product: Product;

}

Свойство компонента product аннотировано @Input(). То есть значение данного свойства будет доступно родительскому элементу, который может связать с ним значение. Мы рассмотрим свойства для ввода информации более подробно в главе 6.

Создайте файл product-item.html, содержащий следующий шаблон компонента product (который будет представлен ценой, заголовком и описанием) (листинг 2.21).

Листинг 2.21. Содержимое файла product-item.html

<div class="thumbnail">

  <img src="">

  <div class="caption">

    <h4 class="pull-right">{{ product.price }}</h4>

    <h4><a>{{ product.title }}</a></h4>

    <p>{{ product.description }}</p>

  </div>

  <div>

    <auction-stars [rating]="product.rating"></auction-stars>

  </div>

</div>

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

Обратите внимание на тег <auction-stars>, представляющий компонент StarsComponent и объявленный в модуле AppModule. Вы привяжете значение свойства product.rating к свойству rating компонента StarsComponent. Чтобы это сработало, рейтинг должен быть объявлен как входное свойство в элементе StarsComponent, который вы создадите далее.

Компонент stars

ch02_05.tif 

Рис. 2.5. Компонент stars

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

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

Создайте файл stars.ts со следующим содержимым в каталоге stars (листинг 2.22).

Листинг 2.22. Содержимое файла stars.ts

72843.png 

77904.png 

Свойство count содержит общее количество звездочек, которое требуется отрисовать. Если это свойство не было инициализировано предком, то компонент по умолчанию отрисует пять звезд. Свойство rating хранит средний рейтинг, определяющий, сколько звездочек должны быть закрашены, а сколько — остаться пустыми. В массиве stars элементы со значением false представляют пустые звездочки, а элементы со значением true заполнены цветом.

Вы инициализируете массив stars в методе обратного вызова ngOnInit(), который будет использован в шаблоне для отрисовки звездочек. Метод ngOnInit() вызывается всего однажды, сразу после того, как привязанные к данным свойства будут проверены в первый раз, и до того, как будут проверены его потомки. Когда вызывается метод ngOnInit(), все свойства, передающиеся от представления-предка, уже инициализированы, поэтому можно применять значение рейтинга для вычисления значений в массиве stars.

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

Создайте шаблон компонента StarsComponent в файле stars.html, как показано далее (листинг 2.23).

Листинг 2.23. Содержимое файла stars.html

<p>

  <span *ngFor="let star of stars"

        class="starrating glyphicon glyphicon-star"

        [class.glyphicon-star-empty]="star">

  </span>

  <span>{{ rating }} stars</span>

</p>

Вы уже пользовались директивой NgFor и выражением для привязки данных в фигурных скобках в компоненте ApplicationComponent. Здесь вы привязываете имя класса CSS к выражению: [class.glyphicon-star-empty]="star". Если правая часть выражения внутри двойных кавычек имеет значение true, то класс CSS glyphicon-star-empty будет добавлен к атрибуту class элемента <span>.

Копируем остальной код

Чтобы завершить этот проект, скопируйте недостающие компоненты из каталога chapter2/auction в соответствующие каталоги вашего проекта.

Каталог services содержит файл product-service.ts, в котором объявляются два класса: Product и ProductService. Здесь будут храниться данные аукциона. Более подробно содержимое данного файла мы рассмотрим в разделе «Практикум» главы 3.

• Каталог navbar содержит код верхней панели навигации.

• Каталог footer содержит код нижнего колонтитула страницы.

• Каталог search содержит исходный код компонента SearchComponent, представляющего собой форму, которую вы разработаете в главе 7.

• Каталог carousel содержит код, реализующий ползунок Bootstrap, расположенный в верхней части страницы.

2.5.3. Запуск онлайн-аукциона

Чтобы запустить онлайн-аукцион, откройте командное окно и активируйте live-сервер в каталоге вашего проекта. Вы можете сделать это, введя команду npm start, которая сконфигурирована в файле package.json. Откроется окно браузера, и вы сможете увидеть главную страницу, как показано на рис. 2.4. Страница, содержащая подробную информацию о продукте, еще не реализована, поэтому ссылки, расположенные в заголовке продукта, не будут работать.

Мы рекомендуем использовать для разработки браузер Chrome, поскольку он предоставляет лучшие инструменты для отладки кода. Откройте панель Developer Tools (Инструменты разработчика) и не закрывайте ее, пока запускаете все фрагменты кода. Если увидите неожиданные результаты, то во вкладке Console (Консоль) сможете найти сообщения об ошибке.

Кроме того, для браузера Chrome существует расширение под именем Augury, позволяющее выполнять отладку приложений Angular. После его установки вы увидите на панели Developer Tools (Инструменты разработчика) дополнительную вкладку Augury (рис. 2.6), которая дает возможность просматривать и изменять значения компонентов вашего приложения во время его работы.

ch02_06.tif 

Рис. 2.6. Вкладка Augury

2.6. Резюме

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

Angular-приложение представлено иерархией компонентов, которые упаковываются в модули.

• Каждый элемент Angular содержит шаблон для отрисовки пользовательского интерфейса и аннотированный класс, реализующий функциональность этого компонента.

• Шаблоны и стили могут находиться как в одном файле, так и в отдельных.

• Загрузчик модулей SystemJS позволяет разбить приложение на модули ES6 и динамически собрать их воедино во время выполнения.

• Параметры конфигурации для SystemJS могут быть указаны в отдельном конфигурационном файле.

• Использование npm для управления зависимостями — самый простой способ конфигурирования нового проекта Angular.

Назад: 1. Знакомство с Angular
Дальше: 3. Навигация с помощью маршрутизатора Angular

32
32
Alex
32
fagmefs
kamagra 100 mg on line
lasix other names
Amoxicillin 250 5ml
dragzolotoru
Ювелирные изделия, те что покупатели имеем или приобретаем именно на подарок родным несут внутри себе большое число увлекательных данных, которые реально просто изучить, когда Вы нажмет на источник новых публикаций касательно драгоценных изделий виды плетение цепочек. Интернет страничка золотых украшений ознакомит любителей из спектр полезными умений, с пособием их Вы могут хорошо ориентироваться у высококачества металлических плюс популярных изделий, к тому же дорогих породах камней плюс металлах. Онлайн сайт всякой проверенных данных о украшения ежедневно увеличивает сведения, те что смогут Вам вернее разбираться какие именно изделия совмещаются в стиле, как же требуется чистить про ювелирными предметами, и что сегодня актуально. Переходите на сайт, читайте также лайкайте подходящие вам новости или же делитесь ссылки на странички соц. сети, здесь на сайте мы будем систематически дополнять существующие библиотеку постов ювелирных украшений.
CharlesTut
импорт товаров
JacobBal
Каждый человек способен испытать испуг в разных жизненных ситуациях. Это – абсолютно обычное явление, помогающее нам спасти себе жизнь в момент угрозы жизни. Правда большинство страхов это просто напросто иррациональное переживание. Например страх летать на самолёте. С этими страхами возможно будет бороться успешно, существуют очень действенные технологии, про них узнаете в анализе ссылка на статью Также можно почувствовать страх опасности, именно это ощущение помогает спасти человеческую жизнь в угрожающих ситуациях. Соответственно страх это адекватное состояние, проблема может возникнуть только лишь если подобный страх часто сводит с ума. В этом случае надо предпринимать определенные меры, обращаться в психологический центр.
Caseybup
електро тепла підлога
Howardton
электро полы с подогревом