Angular 5

в 17:57, , рубрики: angular, angular-cli, angular5, quickstarter, TypeScript
Angular 5 - 1

Введение

1 ноября 2017 года Google анонсировали мажорную версию Angular 5.0.0 под кодовым названием «пятиугольный пончик». Новая версия включает в себя новые функции и исправления ошибок, и в тоже время основной упор был снова сделан на то, чтобы уменьшить размер Angular, сделать его быстрым и простым в использовании. Полное описание всех изменений, включая критические, можно посмотреть в changelog файле официального репозитория Angular.

От переводчиков

Всем привет, с вами Максим Иванов и Дмитрий Сергиенков, и сегодня мы поговорим о вышедшей новой версии, кратко рассмотрим некоторые из наиболее важных изменений и вспомнить историю Angular. Также для тех кто только начинает изучать этот фреймворк, мы рассмотрим примеры того, как быстро разворачивать приложение на Angular. Хотелось бы отметить, что вы можете присоединиться к отечественному сообществу Angular в Telegram, а также посещать Angular Meetup в Москве.

  1. История Angular
    1.1. AngularJS и остальные
    1.2. Почему нам все еще нравится Angular 1.x?
    1.3. И все же Angular не jQuery
    1.4. Все, что вам нужно, это… ядро Angular
    1.5. Так почему мы ушли от AngularJS?
    1.6. Что сделал AngularJS для Web?

  2. Angular QuickStart
    2.1. Зачем нам TypeScript?
    2.2. Шаг 1. Настройка окружения
    2.3. Шаг 2. Создание нового проекта
    2.4. Шаг 3: Запуск веб-приложения в режиме разработки
    2.5. Шаг 4: Редактирование своего первого Angular компонента
    2.6. Что дальше?
    2.7. Каталог проекта
    2.8. Angular CLI vs. Manual Setup

  3. Новшества Angular 5
    3.1. Улучшенный компилятор
    3.2. Оптимизированная сборка
    3.3. Улучшенный server-side rendering в Angular Universal
    3.4. Улучшенная производительность при работе с формами
    3.5. Переписанные под локализацию Pipes
    3.6. Замена ReflectiveInjector на StaticInjector
    3.7. Улучшенный NgZone
    3.8. Multi exportAs
    3.9. Улучшенный RxJS
    3.10. Обновленный цикл жизни у Router
    3.11. Улучшенный Mobile Experience
    3.12. Breaking Changes и не только

1. История Angular

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

Angular — это JavaScript-фреймворк с открытым исходным кодом, обеспечивающий все необходимое для создания клиентской логики вашего приложения:

  • Тот факт, что Angular поддерживается Google, внушает доверие сам по себе;
  • Данный фреймворк спроектирован таким образом, чтобы вы могли легко перейти на него с других языков программирования, не сломав себе психику;
  • Многие разработчики отмечают, что если ваш код на Angular выглядит сложным, то это значит, что вы делаете что-то неправильно;
  • Большое количество веб-сайтов и приложений, написано на AngularJS и Angular: YouTube (для PS3), GoodFilms, Freelancer, Upwork, и другие.

1.1. AngularJS и остальные

Почти каждый разработчик знает, что AngularJS был одним из первых JavaScript-фреймворков, необходимых для создания одностраничных приложений (SPA). В настоящее время мы не удивляемся появлению SPA, они повсюду. Но в 2012 году (AngularJS 1.0.0) это было нечто новое. AngularJS был детищем Google, впервые он был выпущен в 2009 году как фреймворк с открытым исходным кодом под лицензией MIT.

Angular 5 - 2

Согласно SimilarTech.com, который замеряет использование веб-технологий, ReactJS в настоящее время используется на 112k веб-сайтах (при 3.20% росте в октябре 2017 года), а AngularJS (сюда же входит и Angular) используется 542k сайтами (при росте 1.93%).

Angular 5 - 3

Правда, стоит отметить рост VueJS за последнее время, который используется на текущий момент на 19k сайтах (при росте 28.3%).

Многие скажут, что неправильно сравнивать библиотеку и фреймворк (и отчасти вы окажетесь правы, Angular vs React), но люди имеют на то право. Если мы обратим внимание на jQuery, который используется на 70 млн. сайтах (при росте 0.16%), и начнем его сравнивать со всем подряд, то конечно, он всегда будет в лидерах, однако, для того же Enterprise очевидно, что мы выберем что-то посерьезней. Забавно, что Mootools тоже стал набирать популярность в рассматриваемом периоде (рост 18.5%).

Angular 5 - 4

1.2. Почему нам все еще нравится Angular 1.x?

  • На AngularJS легко было начать писать приложения

Да, было очень легко создавать новый проект. Просто вставляем ссылку на CDN, добавляем ng-app в html тег, быстро изучаем какой-нибудь туториал и новое приложение готово. Официальные модули, такие как ngAnimate и ngRouter, быстро решали все наши проблемы.

  • AngularJS Docs и Tutorial

У AngularJS была очень хорошая документация и множество потрясающих туториалов. После того же «Phonecat App» вы прекрасно понимали, что такое AngularJS и как его использовать. На самом деле, он был куда понятнее, чем «Tour of Heroes» (текущий туториал написан под Angular2, quick start которого использует SystemJS, а Angular CLI использует Webpack). Хотя для нового Angular скоро решится и эта проблема.

  • AngularJS был модульным с самого начала (1.x versions)

Вы помните время, когда все использовали Browserify и Bower? Тогда еще не было таких систем сборки, как Webpack. Да, в то время, конечно же, существовал Grunt и Gulp, но они были предназначены для использования, например, gulp-browserify. Так мы формально использовали Browserify. И все же, ng.module от AngularJS была очень важной особенностью. Вы могли просто вставить ссылки на свои скрипты, и они интерпретировались как модули одного приложения. Это была замечательная модульная система. В отличие от jQuery ($.fn.myNewAwesomePluginForJQueryThatNobodyDownloads), AngularJS предоставлял вам возможность писать свои собственные модули и использовать их в качестве плагинов или частей вашего приложения. Кроме того, это было очень удобно в виде lazy-loading.

1.3. И все же Angular не jQuery

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

Angular 5 - 5

  • Двустороннее связывание объектов и модели было «killer feature» или просто «killer»?

ng-model — связывал ваш объект со $scope-объектом и DOM-элементом — это было «killer feature» и «killer» одновременно:

1) Вы могли легко реализовать двустороннюю привязку данных — «killer feature»;
2) Неопытные разработчики привязывали каждую переменную к $scope-объекту в контроллере и после этого жаловались: «А почему мое приложение так медленно работает?» — «killer».

Одностороннее связывание данных было введено еще в версии 1.2, но не все разработчики знали об этом.

  • jqLite — миниатюрная альтернатива jQuery внутри вашего фреймворка

jqLite — это крошечное, API-совместимое подмножество jQuery, которое позволяло AngularJS кроссбраузерно манипулировать с DOM. jqLite реализовал только наиболее часто используемые функции с целью покрывать мелкие требования. Основной парадигмой являлись директивы и компоненты.

Таким образом, о вас позаботились и вам не нужно было включать полную версию jQuery. jqLite предоставляет все, что вам нужно. Но подождите… Вам это действительно нужно?

DOM-манипуляции были вынесены в небольшую библиотеку, доступ к которым возможен был только через angular.element. Это был шаг к новому уровню абстракции? Может быть.

  • Компонентная модель

Первая попытка реализации компонентной модели была в AngularJS. Но в отличие от ReactJS, AngularJS был «JS в HTML», а не «HTML в JS». Даже девиз AngularJS говорил сам за себя: «AngularJS — улучшенный HTML для веб-приложений!».

Почему «JS в HTML»? Все просто, вы использовали директивы, такие как ng-click, ng-for и ng-class в файлах .html вашего приложения.

Директивы были первой попыткой разделить часто используемые компоненты в небольших модулях. Однако, эта затея потерпела неудачу из-за React-подобных компонентов, реализованных в Angular 1.5. Возможно, React и выиграл в этой борьбе, потому что однажды ваш file-name.html мог стать таким (плохой код, который показывает самую большую проблему при работе с Angular):

image

1.4. Все, что вам нужно, это… ядро Angular

  • Встроенные директивы

Нет необходимости изобретать велосипед, когда у вас уже есть все необходимые директивы. По собственному опыту, некоторые разработчики вообще не создавали директивы. Они просто использовали встроенные директивы AngularJS, как упомянуто выше ng-click и ng-for.

  • Внедрение зависимостей в JavaScript

Одной очень интересной особенностью AngularJS является dependency injection. Кстати, вы когда-нибудь видели его в других проектах, за исключение AngularJS?

  • AngularJS огромен

И на самом деле нет никакого смысла здесь рассматривать следующие вопросы:
1) «Services, factories, providers в AngularJS»;
2) «Как уменьшить Angular-приложение?»;
3) «Как работает $scope.$watch?»;
4) «Как работает AngularJS?»;
5) «Встроенный $http, фильтрация и производительность ng-for»;
6) «Какова была самая большая проблема с AngularJS?»;
… длинный список

Некоторые вопросы настолько велики, что можно проводить академическое исследование по ним.

1.5. Так почему мы ушли от AngularJS?

1) Не очень хорошая производительность из коробки;
2) Большая часть функционала из AngularJS не использовалось на практике;
3) AngularJS работал напрямую с DOM, а не c VirtualDOM, к примеру;
4) Низкий уровень абстракции, в AngularJS практически вся логика была завязана на HTML;
5) Трудно быть экспертом в AngularJS, если сам фреймворк огромен;
6) Современный Web требовал нового подхода.

1.6. Что сделал AngularJS для Web?

Это простой и сложный вопрос одновременно.

  • JS === Power

Возможно, AngularJS стал первым инструментом, который доказал, что JavaScript мощный язык. Обычно можно услышать, что «JavaScript — это язык для детей» или «JS — это язык для создания глупой анимации», все эти мифы были уничтожены силами AngularJS.

  • Сообщество, эксперты, экосистемы

1) Хороший фреймворк => много разработчиков;
2) Много разработчиков => Большое сообщество;
3) Большое сообщество => Дополнительные компоненты, инструменты, туториалы;
4) Большое количество инструментов и практик => Много экспертов;
5) Много экспертов => Новые идеи, модули, библиотеки;
6) Чистый прогресс...

  • Vue, Aurelia, Angular 2/4/5

Все эти фреймворки были вдохновлены AngularJS. Aurelia это ответвление (fork) AngularJS. Angular (Angular 2+) является продолжением AngularJS. Некоторые разработчики называют VueJS новым AngularJS из-за v-if, v-bind, v-on и других директив.

  • Вы все еще помните AngularJS?

2. Angular QuickStart

Хорошие инструменты для разработки делают вашу работу более комфортной, даже если вам нравится делать все вручную. Angular CLI — это интерфейс командной строки, который позволяет вам быстро создавать проекты, добавлять файлы и выполнять множество определенных задач, такие как тестирование, сборка и развертывание.

Основная цель этого раздела — создать и запустить простое Angular-приложение на TypeScript с использованием Angular CLI и в дальнейшем придерживаться официального Style Guide руководства, которые специфицирует и стандартизирует проекты на Angular. В конце концов, вы будете иметь базовое понимание того, как разрабатывать приложения с помощью CLI. Однако, сначала определимся, зачем нам TypeScript?

2.1. Зачем нам TypeScript?

Angular 5 - 7

TypeScript — это JavaScript (ES6/ES7/ES8/..) + информация о типах. То есть если вы знаете ES6+, то вы автоматически знаете TypeScript (добавится только информация о типах и некоторые нюансы, но в целом они строго следуют стандарту). TypeScript — это не Dart, который является другим языком. Это надстройка над JavaScript, которая позволяет в рамках очень больших проектов (таких, как сам Angular, который сам по себе очень большой проект) улучшить управление сложностью, получить статический анализ, мы имеем возможность пользоваться аннотациями, типизацией, наследованием, интерфейсами, а самое главное — модулями. В крупных проектах эта информация действительно позволяет избавиться от огромного набора ошибок. В интернете существует достаточно большое количество примеров и руководств по написанию приложений на TypeScript, но в нашем случае мы подготовили вам базовый starter kit, который работает под управлением системы сборки Webpack.

2.2. Шаг 1. Настройка окружения

Перед тем, как начать разрабатывать, вам необходимо настроить себе окружение. Установите Node.js и npm (если вы еще не установили их) себе на компьютер. Убедитесь, что вы используете Node.js версии не ниже 6.9.x и npm не ниже 3.x.x (для этого достаточно выполнить в терминале node -v и npm -v).

Затем установите Angular CLI глобально.

npm install -g @angular/cli

Как правило, этот шаг проделывается один раз, и в будущем его выполнение уже не потребуется.

2.3. Шаг 2. Создание нового проекта

Создайте новое приложение (разворачивание каркаса), выполнив следующие команды:

ng new my-app

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

2.4. Шаг 3: Запуск веб-приложения в режиме разработки

Перейдите в каталог проекта и запустите веб-сервер.

cd my-app
ng serve --open

Команда ng serve запускает веб-сервер, а также прослушивает каталог c исходниками вашего приложения и при изменениях в этих исходных файлах пересобирает проект «на лету». Стоит отметить, что в таком режиме проект не сохраняется на диске, он записывается непосредственно в оперативную память.

Использование ключа --open (или просто -o) означает, что после сборки проекта, автоматически откроется ваш браузер (по умолчанию выбранный в операционной системе).

В открытой вкладке http://localhost:4200/ по умолчанию вы уведите стандартный макет Angular приложения.

Angular 5 - 8

2.5. Шаг 4: Редактирование своего первого Angular компонента

По умолчанию Angular CLI создает для вас базовый компонент. Этот базовый компонент является корневым, он связан с селектором app-root. Вы можете найти его по следующему пути в каталоге ./src/app/app.component.ts.

В этом файле вы увидите следующее:

...

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app';
}

@Component — это один из базовых декораторов в Angular. Декораторы — это альтернатива для аннотаций (пример из Java), предложенная Yehuda Katz в стандарт ECMAScript 2016. Декораторы позволяют не только аннотировать метаданными объекты, но и модифицировать классы, методы и свойства. Также декорирование — это приём программирования, который позволяет
взять существующую функцию и изменить/расширить ее поведение. Во многих случаях код с использованием декораторов будет чище, красивее и эффективнее.

image

Декоратор получает функцию (в нашем случае, класс AppComponent) и возвращает обертку, которая делает что-то своё «вокруг» (magic) вызова основной функции. Что конкретно делает декоратор @Component вы можете посмотреть тут.

Итак, самое очевидное, что вы можете сделать сейчас для своего компонента, это поменять его title, к примеру:

...
export class AppComponent {
  title = 'My First Angular App';
}

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

Откройте файл .src/app/app.component.css и задайте компоненту некоторые стили:

h1 {
  color: #369;
  font-family: Arial, Helvetica, sans-serif;
  font-size: 250%;
}

Angular 5 - 10

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

body {
  background: red;
}

app-root {
  background: blue;
}

то ничего не изменится, фон страницы не будет красным, а фон самого компонента не будет синим. И если мы посмотрим в код страницы, то увидим следующее:

Angular 5 - 11

Дело в том, что Angular по умолчанию инкапсулирует CSS (кроссбраузерно эмулирует Shadow DOM), чтобы стили одного компонента не сломали или переопределили стили другого. Поэтому ни в коем случае не используйте имя селектора компонента или внешние имена селекторов для задания его стилей. Атрибуты для инкапсуляции CSS кода генерируются только для дочерних элементов этого компонента.

Некоторые утверждают, что если на странице используется огромное количество компонентов, Angular проделывает лишнюю работу и нагружает наш процессор, что сказывается на скорости, к тому же, если вы используете какую-нибудь css-методологию (BEM, SMACSS), то вероятнее всего вы напишете хорошую верстку и css код, не требующий для этого инкапсуляции со стороны Angular. Для этого вы можете просто указать дополнительный параметр в декораторе:

@Component({
  ...
  encapsulation: ViewEncapsulation.None
  ...
})

Более подробно вы можете прочитать тут.

2.6. Что дальше?

Шаги, описанные выше — это все, что необходимо для простого «Hello, World». Дальше вы можете попытаться изучить туториал «Tour of Heroes» и создать небольшое приложение, которое поможет в будущем разрабатывать более сложные приложения с помощью Angular, но я бы порекомендовал посмотреть видеокурс на Udemy (неважно, что он на английском, вы сможете разобраться в силу того, что там все доступно показывается). Для ознакомления с командами angular-cli, вы можете перейти на данный русскоязычный ресурс. Сейчас же можно ознакомиться с каталогом проекта.

2.7. Каталог проекта

Angular CLI является удобным инструментом для разработки и развертывания enterprise-решений. Для начала самый первый файл, который вам необходимо изучить это README.md.
Он содержит информацию о базовых командах, которые вы должны знать при использовании Angular CLI. Всякий раз, когда вам потребуется узнать больше, чем есть в README и посмотреть какие-нибудь примеры использования Angular CLI, вам достаточно будет зайти на официальный репозиторий и открыть раздел Wiki.

Некоторые из сгенерированных файлов могут быть вам незнакомы, поэтому поговорим о них подробнее.

Директория src

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

.
|-- app
|   |-- app.component.css
|   |-- app.component.html
|   |-- app.component.spec.ts
|   |-- app.component.ts
|   `-- app.module.ts
|-- assets
|-- environments
|   |-- environment.prod.ts
|   `-- environment.ts
|-- favicon.ico
|-- index.html
|-- main.ts
|-- polyfills.ts
|-- styles.css
|-- test.ts
|-- tsconfig.app.json
|-- tsconfig.spec.json
`-- typings.d.ts

app/app.component.{ts,html,css,spec.ts} — специфицирует AppComponent компонент html-шаблоном, стилями и юнит-тестами. Это корневой компонент, для которого по мере развития приложения появится дерево вложенных компонентов.

app/app.module.ts — специфицирует AppModule. Корневой модуль, который сообщает Angular, как будет собрано приложение. Сейчас в нем объявлен только AppComponent. Впоследствии вы будете объявлять в нем другие компоненты.

assets/* — директория, в которой вы размещаете изображения и все остальное, которую необходимо скопировать в конечную директорию сборки, когда вы создадите непосредственно само приложение.

environments/* — эта директория содержит файлы целей сборки (dev или prod режимы), каждый из которых экспортирует простые env-переменные конфигурации, необходимые для использования в вашем приложении. Когда вы разрабатываете приложение, файлы собираются «на лету». Вы можете использовать разный набор API при разработке (dev), отличный от продакшина (prod), у которого вы можете подключать всякие метрики или auth-токены. Вы даже можете использовать разные сервисы или заглушки при разных целях сборки.

favicon.ico — вы можете добавить свою собственную иконку, которая будет отображаться на вкладке в браузере.

index.html — основная HTML-страница, которая отображается, когда кто-то посещает ваш сайт. В большинстве случаев вам никогда не понадобится его редактировать. Angular CLI автоматически добавляет все сгенерированные js и css-файлы при создании вашего приложения, поэтому вам не нужно добавлять какие-либо теги (script, link) вручную.

main.ts — точка входа вашего приложения. Сейчас, по умолчанию, ваше приложение компилируется в поставке с JIT-компилятором. Данный файл загружает корневой модуль приложения (AppModule) и запускает его в браузере. Вы также можете использовать AOT-компилятор, заранее скомпилировав свое приложение, исключив JIT-компилятор из сборки, для этого вы должны использовать флаг —aot для команд Angular CLI ng build и ng serve.

polyfills.ts — различные браузеры имеют различные уровни поддержки тех или иных веб-стандартов. Полифиллы помогают нормализовать эти различия. Однако не забывайте проверять текущую поддержку браузерами.

styles.css — здесь хранятся глобальные стили. Большую часть времени вы будете работать с локальными стилями своих компонентов, но стили, которые влияют на все ваше приложение, должны находиться в этом месте.

test.ts — это точка входа всех ваших юнит-тестов. Этот файл имеет настраиваемую конфигурацию, но, как правило, вы будете редко его править.

tsconfig.{app|spec}.json — конфигурация компилятора TypeScript описывается в файле tsconfig.app.json, для юнит-тестов же используется конфигурация tsconfig.spec.json.

Корневая директория проекта

src/ является только одним из элементов корневой директории проекта. Другие файлы помогают собирать, тестировать, поддерживать, документировать и развертывать приложение.

.
|-- README.md
|-- e2e
|-- karma.conf.js
|-- node_modules
|-- package-lock.json
|-- package.json
|-- protractor.conf.js
|-- src
|-- tsconfig.json
`-- tslint.json

e2e/ — внутри директории e2e/ располагаются e2e (end-to-end) тесты. Они не должны находиться внутри директории src/, поскольку эти тесты представляют собой отдельное приложение, которое тестирует ваше основное приложение. Внутри располагаются конфигурационные файлы, например, tsconfig.e2e.json.

node_modules/ — Node.js создает данную директорию, в которой хранит все сторонние модули, перечисленные в package.json.

.angular-cli.json — конфигурационный файл Angular CLI. В этом файле вы можете установить несколько значений по умолчанию, а также настроить, какие файлы будут включены при сборке проекта.

.editorconfig — Простая настройка для вашего редактора, специфицирующая одинаковую базовую конфигурацию для форматирования текста кода. Большинство редакторов поддерживают файл .editorconfig. Дополнительную информацию смотрите на странице http://editorconfig.org.

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

karma.conf.js — конфигурационный файл для запуска юнит-тестов с использованием Karma, запуск тестов можно выполнить командой ng test.

package.json — конфигурационный файл npm, в нем перечисляются сторонние модули (пакеты) разработчиков, которые использует ваш проект. Здесь вы также можете прописать и свои собственные скрипты.

README.md — основная документация для вашего проекта, предварительно заполненная информацией Angular CLI.

tsconfig.json — конфигурация компилятора TypeScript для вашей IDE.

tslint.json — конфигурация для статического анализатора TSLint, используется при запуске ng lint. Для чего нужен анализатор вы можете посмотрев на пример.

Developer Experience

Angular 5 - 12

2.8. Angular CLI vs. Manual Setup

Существует множество способов, которые вы можете опробовать для создания нового проекта на Angular. Но на самом деле существует два основных подхода, первый из них — это использование Angular CLI, а второе — ручная настройка Webpack.

Angular CLI

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

Плюсы:

  • Быстрая и простая в установке, идеально подходит для начинающих
  • Является надстройкой над Webpack
  • Включает в себя инструменты тестирования Protractor e2e, Karma, Jasmine
  • Не нужно самостоятельно следить за зависимостями
  • Централизованный конфигурационный файл
  • Быстрые и простые CLI-команды для запуска приложения, создания новых компонентов и многое другое

Минусы:

  • Менее расширенная конфигурация (невозможно изменить встроенную конфигурацию Webpack)
  • Жесткая структура конфигурационных файлов (несколько tsconfig файлов, которые сложнее перемещать из директории в директорию)
  • Слабая поддержка или полное отсутствие плагинов (невозможно, к примеру, добавить Pug-шаблонизатор)

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

Webpack

В этом случае ручная настройка с использованием Webpack фактически является противоположностью плюсам и минусам Angular CLI. Однако одним из лучших альтернатив Angular CLI, с точки зрения гибкости настройки, является проект AngularClass Angular Webpack Starter. Однако за счет легкого конфигурирования webpack вы можете настроить простое окружения для своего проекта, оставив только необходимое.

Плюсы:

  • Расширенная настройка Webpack
  • Возможность использования любых HTML-шаблонизаторов
  • Чистый конфигурационный файл и структура каталогов
  • Большая гибкость (конфигурационные файлы являются javascript файлами, для которых можно писать свою логику)

Минусы:

  • Трудность настройки эффективного e2e-тестирования
  • Возможность расширенной конфигурации увеличивает вероятность конфигурационных ошибок
  • Проблемы с зависимостями и их upgrade-обновлениями

SystemJS

SystemJS позиционирует себя как универсальный динамический загрузчик модулей — модулей ES6, AMD, CommonJS и глобальных браузерных или Node.js пакетов. Поскольку вы используете модули в TypeScript, так же, как в ES6, вам нужен загрузчик модулей (в Webpack за это отвечают loaders). В случае с SystemJS вы пишете конфигурационный systemjs.config.js, который позволяет вам настроить способ сопоставления имен модулей с их соответствующими файлами в каталоге node_modules или где-либо еще.

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

<script>
  System.import('app').catch(function(err){ console.error(err); });
</script>

Причем забегая вперед, для TypeScript конфигурации вы указывали определенный параметр типа модуля:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs", // <------
    ...

Таким образом, после компиляции из TypeScript в ваших JavaScript файлах вы увидели бы внутри особые функции require(). Если бы указали "module": "es6", вы увидели бы в скомпилированных файлах ключевые слова import, export. Однако, вы все еще не можете использовать этот синтаксис, поскольку полной поддержки браузерами еще нет. Если бы указали тип "module": "amd", вы увидели бы другой синтаксис, который использовал функции define(). Стоит отметить, что в обучающем туториале на официальном сайте «Tour of Heroes» до сих пор в примерах участвует SystemJS, однако, после выхода Angular 5, разработчики вскоре поправят документацию и заменят на примеры с Angular CLI.

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

Webpack это достаточно гибкая система сборки. Он не только обрабатывает какие-либо типы модулей (ES6, AMD, CommonJS), но и позволяет выполнить прилегающие задачи, обеспечивая выполнение сжатия, минификации каких-либо файлов, работу с картинками, css-препроцессорами и многое другое. Он также предоставляет веб-сервер для разработки в режиме инкрементальной сборки. Когда вы используете Webpack, SystemJS становится избыточным. Webpack готовит для вас как минимум один файл с возможным именем bundle.js. Этот файл может содержит все необходимые HTML, CSS, JS составляющие ваших веб-компонентов. Поскольку все файлы объединены в один файл, то нет жесткой необходимости в ленивом загрузчике, такой как у SystemJS. Главный потенциал SystemJS был в ленивой загрузке ваших файлов. Приложение должно было загружаться быстрее, потому что вы не загружаете один большой бандл, но на практике это оказалось не очень выгодным решением.

Но даже сейчас Webpack позволяет использовать вынесение кода из общего бандла (code splitting) и подгружать их при помощи ленивой загрузки по требованию, а также представляет улучшенный Tree Shaking для ваших модулей. Поэтому начиная с версии 1.2, Angular CLI использует внутри себя реализацию Webpack.

На самом деле, уже практически невозможно найти какой-либо работающий Starter Kit, использующий SystemJS, многие либо переписали на Webpack, либо забросили. Но даже если вы и найдете его, он будет скорее всего с версией Angular 2. Поэтому настоятельно рекомендую изучать первые два способа сборки ваших приложений на Angular.

Rollup

Здесь мы не будем много говорить на эту тему. Хотя есть примеры (хоть и в сравнении с Webpack второй версии), какой код генерирует Webpack, а какой генерирует Rollup на выходе, и тут последний куда лучше. При этом стоит снова отметить, что Webpack — это не просто сборщик модулей, а куда больше. Поэтому Webpack, как правило, лучше подходит для разработки приложений, а Rollup, как правило, лучше подходит для разработки библиотек.

3. Новшества Angular 5

Те, кто уже успел обновиться со второй версии на четвертую, не могли не заметить, насколько просто это было сделать. В пятой версии хотят продолжить эту приятную традицию и сделать все, чтобы переход с Angular 4 на Angular 5 произошел как можно более незаметно. В будущем будет реализована система автоматического обновления, встроенная в Angular CLI. Сейчас же, либо вам необходимо использовать Angular CLI 1.5+, либо самостоятельно обновить свои пакеты в проекте.

$ npm install @angular/{animations,common,compiler,compiler-cli,core,forms,http,platform-browser,platform-browser-dynamic,platform-server,router}@5.0.0

# или Yarn:
$ yarn add @angular/{animations,common,compiler,compiler-cli,core,forms,http,platform-browser,platform-browser-dynamic,platform-server,router}@5.0.0

# вместо указания @5.0.0, можно просто прописать @latest

3.1. Улучшенный компилятор

TypeScript Transforms

Angular 4:

В инкрементальной сборке Angular-компилятор (ngc) перекомпилировывал все файлы при каждом изменении, что замедляло процесс разработки.

Angular 5 - 13

Angular 5:

Теперь представляет улучшенную watch-опцию, которая перекомпилирует только то, что нужно.

Angular 5 - 14

Это означает, что для средних проектов время компиляции сократилось с 12-14 секунд до 2-3 секунд.

Теперь у вас есть возможность использования режима инкрементальной сборки совместно с AOT-компиляцией, благодаря которому вы сможете находить проблемные участки кода вашего приложения еще на стадии разработки. Самые частые ошибки, которые возникали у кого-либо, это работа с Lazy Modules в разных режимах сборки. В AngularJS и в Angular 2 (изначально), была только JIT-компиляция, в будущем был добавлен новый тип компиляции, в более старших версиях Angular режим AOT будет включен по умолчанию, сейчас для этого нужно либо указывать специальный флаг (Angular CLI), либо использовать специальный loader для Webpack.

ng serve --aot

Однако есть вероятность что-то может пойти не так, если вы раньше собирали свое приложение только в режиме JIT:

ng build --prod --aot=false --build-optimizer=false
# вместо: ng build --prod --aot=false 

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

  • Шаблоны компонентов компилируются во время выполнения (Just in time, JIT)

Angular 5 - 15

  • Шаблоны компонентов компилируются заранее до выполнения (Ahead of time, AOT)

Angular 5 - 16

В чем отличие JIT от AOT вы можете посмотреть здесь.

С выходом Angular CLI 1.5 у вас будет уже меньше трудностей при разработке, вам больше не нужно беспокоиться о том, в каком режиме вы собираете свое приложение, и будут ли какие-либо последствия, если вы собрали что-то не в том режиме. Теперь вы всегда можете использовать AOT.

Preserve Whitespace

Была добавлена новая опция к декоратору Component, которая сокращает размер ваших шаблонов за счет удаления ненужных пробелов.

Например, у вас был такой код в вашем компоненте в шаблоне app.component.html:

Angular 5 - 17

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

Angular 5 - 18

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

tsconfig.json

{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "outDir": "../out-tsc/app",
    "baseUrl": "./",
    "module": "es2015",
    "types": []
  },
  "angularCompilerOptions": {
    "preserveWhitespaces": false // <------
  },
  "exclude": [
    "test.ts",
    "**/*.spec.ts"
  ]
}

Или в main.ts

platformBrowserDynamic().bootstrapModule(AppModule, { preserveWhitespaces: false });

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

Существует также новая директива под названием ngPreserveWhitespaces, если вы хотите где-либо сохранить пробелы в фрагментах вашего шаблона, теперь вы можете использовать это так:

<div ngPreserveWhitespaces>hi   !+!  panda</div>  

Улучшенная поддержка декораторов

В новой версии Angular стали доступными анонимные функции, позволяющие запускать код, не затрагивая d.ts и открытый API. Это позволяет использовать значения, которые могут быть вычислены только на этапе выполнения в runtime.

Раньше необходимо было передавать фабрику:

export function webSocketFactory() {
  return WebSocket;
}

@NgModule({
  providers: [
    { provide: WEBSOCKET, useFactory: webSocketFactory },
  ]
})
export class AppModule {}

Теперь:

@NgModule({
  providers: [
    { provide: WEBSOCKET, useFactory: () => WebSocket },
  ]
})
export class AppModule {}

Проверка типов в шаблонах

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

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

<!-- lowercase expects a string -->
<div>{{ 12.3 | lowercase }}</div>

В отладчике вы увидете:

Argument of type '12.3' is not assignable to parameter of type 'string'

Компилятор также может анализировать локальные переменные, ссылающиеся на директивы внутри ваших шаблонов. Например, предположим вы создали ссылку на ngModel и по этой ссылке захотели получить доступ к методу hasError(), но сделали опечатку:

<input [(ngModel)]="user.password" required #loginCtrl="ngModel">
<!-- typo in `hasError()` method -->
<div *ngIf="loginCtrl.hasEror('required')">Password required</div>

В отладчике вы увидете:

Property 'hasEror' does not exist on type 'NgModel'. Did you mean 'hasError'?

3.2. Оптимизированная сборка

Начиная с Angular 5.0.0, production builds, созданные с помощью Angular CLI, теперь будут применять улучшенные оптимизации кода по умолчанию.

Build optimizer — это инструмент, включенный в поставку Angular CLI, позволяющий уменьшить размер вашего приложения, используя семантический анализ всего приложения.

Выступая на AngularMoscow в июле 2017 года в московском офисе Google с докладом об Angular 4, я проводил исследование того, как улучшается время запуска и скорость работы нашего приложения при переходе с Angular 2 на Angular 4. Сейчас же, в рамках выхода новой версии, я замерил то же самое приложение, но уже работающее на Angular 5. И результаты меня впечатлили, я думаю, стоит задуматься, а не перейти ли на Angular 5 поскорее.

Angular 5 - 19

3.3. Улучшенный server-side rendering в Angular Universal

Angular Universal — проект, призванный помочь разработчикам осуществлять рендеринг на сервере (SSR) в Angular-приложениях. Рендеринг приложений на сервере с их последующей загрузкой в сгенерированный HTML существенно облегчает нахождение соответствующего контента поисковыми роботами, что положительно сказывается на производительности.

Приближенная схема использования SSR в Enterprise-решениях:

Angular 5 - 20

В данном случае для разработки был использован Nodemon (хотя обычно это бывает Express для Node.js), он выполняет простую функцию отдачи отрендеренной на сервере страницы посредством технологии server-side-rendering (можете представить это как headless режим, который исполняет вашу страницу и отдает статику), либо отдает страницу сразу из кэша, если уже была до этого отрендерена на сервере. Сам веб-сервер на Node.js для компонентов может также общаться с другими сетевыми ресурсами вашей бизнес-логики (Java, ASP.NET, ..) посредством проксирования (Nginx, HAProxy). Вы можете обрабатывать бизнес-логику как на стороне клиента, так и на стороне вашего веб-сервера Node.js. Ваш веб-сервер также может отрендерить страницы в зависимости от того, с какого устройства подключаются к ресурсу, это означает, что если ваши пользователи используют какое-нибудь гибридное мобильное приложение, их страницы могут быть легковесными, в то время как они получают их с удаленных ресурсов.

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

Мы подготовили базовый starter kit, который доступен по ссылке. Также есть возможность посмотреть примеры на официальном репозитории или из других источников. Также вы можете посмотреть обзорное видео Angular Universal Page Load Performance.

Angular Universal State Transfer API and DOM Support

Теперь можно легко обмениваться состоянием между серверной и клиентской версиями приложения. Были добавлены ServerTransferStateModule и соответствующий BrowserTransferStateModule. Наличие этих модулей устраняет необходимость посылать второй HTTP-запрос клиенту со стороны приложения. Еще одно примечательное обновление — добавление библиотеки Domino к platform-server. Оно сделало доступным большее число манипуляций с DOM на стороне сервера, что улучшило поддержку сторонних сервер-не-ориентированных JS-библиотек и улучшило производительность.

3.4. Улучшенная производительность при работе с формами

Как правило, Enterpise User Interface представляет из себя огромное количество полей форм, и если все это будет плохо работать и тормозить, тогда никому такой UI не нужен будет. Поэтому теперь валидацию и обновление значений в формах можно применять только для blur или submit, а не всех событий ввода. Теперь разработчики могут определять только те области, которые им очень важны с точки зрения производительности. Также asyncValidator теперь можно определять прямо в объекте options.

Angular 4:

Валидация форм происходила при каждом изменении значений в формах:

Angular 5 - 21

Anguar 5:

Введена новая директива updateOn:

Angular 5 - 22

Angular 5 - 23

Все это может быть настроено для всех полей одновременно:

Angular 5 - 24

Или прописать в ngModel:

Angular 5 - 25

Или прописать в ngFormOptions:

Angular 5 - 26

3.5. Переписанные под локализацию Pipes

Разработчики представили обновленные Pipes (конвейеры) для Date, Number, Percent и Currency, устранив необходимость в использовании полифиллов i18n.

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

Angular 5 - 27

Angular 5 - 28

Но вы можете менять локализацию и динамически:

Angular 5 - 29

3.6. Замена ReflectiveInjector на StaticInjector

Избавиться от еще большего количества полифиллов помогла замена ReflectiveInjector на StaticInjector. Новому injector’у больше не нужен полифилл Reflect. Однако обратите внимание, что Reflect по-прежнему необходим, если вы собираете свои приложения в режиме JIT.

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

Angular 5 - 30

ReflectiveInjector

ReflectiveInjector опирается на возможности рефлексии, предоставляемые библиотекой Reflect, для извлечения неявных зависимостей для провайдера, отсюда и название Reflective.

class B {}
class A {
  constructor(@Inject(B) b) { } <----- `B` неявная зависимость
}

const i = ReflectiveInjector.resolveAndCreate([A, B]);

Декоратор @Inject определяет метаданные, определяющие неявные зависимости, а затем ReflectiveInjector использует эти метаданные.

StaticInjector

В этом случае вы не используете рефлексивные зависимости, что влечет за собой явное указание зависимостей:

class B {}
class A { constructor(b) {} }
const i = Injector.create([{provide: A, useClass: A, deps: [B]]};
const a = i.get(A);

То есть, если раньше вы могли писать код так:

class B1 {}
class A1 { constructor(@Inject(B1) b) {} }

class B2 {}
class A2 { constructor(@Inject(B2) b) {} }
bootstrapModule(AppModule, {providers: [A1, B1]}).platformBrowserDynamic([A2, B2])

Теперь же рекомендуется использовать такой подход:

class B1 {}
class A1 { constructor(b) {} }

class B2 {}
class A2 { constructor(b) {} }

platformBrowserDynamic([{ provide: A1, useClass: A1, deps: [B1] }, B1])
.bootstrapModule(AppModule, { providers: [ {provide: A2, useClass: A2, deps: [B2]}, B2 ] })

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

3.7. Улучшенный NgZone

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

platformBrowserDynamic().bootstrapModule(AppModule, {ngZone: 'noop'}).then(ref => {});

3.8. Multi exportAs

Добавлена поддержка нескольких имен для компонентов проекта. Это поможет пользователям легко переносить компоненты без необходимости вносить какие-либо изменения в код.

Angular 5 - 31

Angular 5 - 32

Дополнительный пример:

@Component({
  moduleId: module.id,
  selector: 'a[mat-button], a[mat-raised-button], a[mat-icon-button]',
  exportAs: 'matButton, matAnchor',
  .
  .
  .
}

3.9. Улучшенный RxJS

Поддержка RxJS обновлена до версии 5.5.2+. С новыми «lettable operators» разработчики могут спокойно избегать проблем, связанных со старым механизмом импорта. Устранены все недостатки предыдущего метода patch, включая проблемы splitting / tree shaking.

import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/do';

@Component({ ... })
export class AppComponent implements OnInit {
  myObs = Observable.of('Hello', 'Alligator', 'World!');

  ngOnInit() {
    this.myObs
      .do(x => console.log('The do operator is the do operator!'))
      .filter(x => x.length > 8)
      .map(x => x.toUpperCase())
      .subscribe(x => console.log(x));
  }
}

Теперь:

import { Component, OnInit } from '@angular/core';
import { of } from 'rxjs/observable/of';
import { map, filter, tap } from 'rxjs/operators';

@Component({ ... })
export class AppComponent implements OnInit {
  myObs = of('Hello', 'Alligator', 'World!');

  ngOnInit() {
    this.myObs
      .pipe(
        tap(x => console.log('The do operator is now tap!')),
        filter(x => x.length > 8),
        map(x => x.toUpperCase())
      )
      .subscribe(x => console.log(x));
  }
}

Обратите внимание, что почти все операторы могут быть импортированы одной строчкой. Для использования lettable-операторов теперь вы работаете с методом pipe, в который передаете свои функции.

Переименованные операторы:
do -> tap
catch -> catchError
switch -> switchAll
finally -> finalize

Подробнее можно почитать тут.

3.10. Обновленный цикл жизни у Router

Разработчики теперь могут отслеживать полный цикл маршрутизатора. Это полезно для измерения производительности guards и/или resolvers или при отображении счетчика во время обновления дочернего элемента.

Новые события (последовательно):
GuardsCheckStart,
ChildActivationStart,
ActivationStart,
GuardsCheckEnd,
ResolveStart,
ResolveEnd,
ActivationEnd,
ChildActivationEnd.

class MyComponent {
  constructor(public router: Router, spinner: Spinner) {
    router.events.subscribe(e => {
      if (e instanceof ChildActivationStart) {
        spinner.start(e.route);
      } else if (e instanceof ChildActivationEnd) {
        spinner.end(e.route);
      }
    });
  }
}

Также заслуживает внимания еще одно нововведение, теперь можно перезагрузить страницу, когда роутер получает запрос на переход по тому же URL-адресу, который установлен.

providers: [
  // ...
  RouterModule.forRoot(routes, {
    onSameUrlNavigation: 'reload' // по умолчанию -> ignore
  })
]

3.11. Улучшенный Mobile Experience

Теперь доступен новый пакет @angular/service-worker, позволяющий полноценно разрабатывать Progressive Web Application (PWA). Однако полная поддержка прогрессивных веб-приложений под улучшенные Material Design компоненты не ожидаются до конца ноября 2017 года. Более подробнее о том, как использовать новый пакет вы можете прочитать тут.

3.12. Breaking Changes и не только

Да, критических изменений немало. Чтобы безболезненно переходить на новые версии, команда Angular подготовила специальный ресурс Angular Update Guide,, который облегчит вам жизнь и улучшит понимание того, почему у вас что-то не работает.

Angular 5 - 33

Улучшенные Pipes

В случае с новыми Pipes: Date, Number, Percent и Currency, если вы хотите использовать старую версию:

@NgModule({
  imports: [CommonModule, DeprecatedI18NPipesModule],
  // ...
})
export class AppModule {

Явное указание зависимостей

Вместо:

platformBrowserDynamic([
  MyCustomProviderA,
  MyCustomProviderB // depends on MyCustomProviderA
]).bootstrapModule(AppModule);

Необходимо явно прописывать зависимости:

platformBrowserDynamic([
  { provide: MyCustomProviderA, deps: [] },
  { provide: MyCustomProviderB, deps: [MyCustomProviderA] }
]).bootstrapModule(AppModule);

HttpClient

Начиная с версии Angular 4.3, появился новый HttpClient. Начиная с версии Angular 6, старый клиент будет удален, в Angular он помечен как deprecated.

HttpClientModule:

Angular 5 - 34

Inject HttpClient:

Angular 5 - 35

Нет больше необходимости извлекать JSON:

Angular 5 - 36

Можем использовать перехватчики (Interceptors):

Angular 5 - 37

Можем добавлять перехватчики к провайдерам:

Angular 5 - 38

Resource integrity

В новой версии Angular CLI 1.5 добавляет к тегам script и link специальные атрибуты integrity и crossorigin. Это необходимо, чтобы современные браузеры проверяли ваши ресурсы на поврежденность, чтобы проверить, не был ли поврежден ресурс на случай атаки «man in the middle». Таким образом, ваши приложения станут намного безопаснее.

Проверка на несоответствие версии TypeScript

В новой версии Angular CLI 1.5 будет предупреждать вас, если вы используете версию TypeScript (вы можете самостоятельно обновить версию TypeScript до последней), которая не рекомендована для использования Angular. Это сделано для того, чтобы избежать некоторой головной боли, которая может возникнуть из-за того, что в новой версии TypeScript что-то пошло не так, к примеру, конфигурационный файл для новой версии не соответствует действительности. В поставке Angular CLI текущая версия TypeScript 2.4. Но если вы захотите самостоятельно обновиться, вы можете отключить предупреждения при помощи команды ng set warnings.typescriptMismatch=false (внимание, команда работает глобально, а не для конкретного проекта).

appRoot configurable

Основное применение этой функции — использование нескольких приложений в одном каталоге src.

В заключении

Вся деятельность команды Angular направлена на увеличение стабильности и производительности самого фреймворка, оптимизационные моменты прямое тому подтверждение. От себя мы можем лишь добавить, что были очень рады стать частью сообщества Angular. Пишите авторам фреймворка, создавайте заметки в Issues Tracker на гитхабе, тем самым вы поможете улучшить фреймворк своими замечаниями и пожеланиями, ведь командная работа, она не только завязана на самом Angular Team, но и на Community. Мы желаем вам всего наилучшего, дорогие читатели, и удачи! Пока!

Angular 5 - 39

Автор: splincodewd

Источник

Поделиться

* - обязательные к заполнению поля