Визуализация данных при помощи angular и d3

Создание собственного источника событий

Задача: Создать переменную в , на которую другие компоненты будут подписываться и реагировать, когда язык будет меняться.


Решение: Воспользуемся классом для создания переменной ;

Отличия от :

  1. должен инициализироваться с начальным значением;
  2. Подписка возвращает последнее значение а;
  3. Можно получить последнее значение напрямую через функцию .

Создаём переменную и сразу инициализируем. Так же добавляем функцию для установки языка.

Подписываеся на изменение языка в компоненте. Переменная является «горячим» Observable объектом, то есть подписка требует отписки при разрушении объекта.

Использование takeUntil для отписки

Отписываться можно и , особенно если в компоненте присутствует больше двух подписок:

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

Работа с HTTP

Построение любого клиентского Web приложения производится вокруг HTTP запросов к серверу. В этой части рассматриваются некоторые возможности фреймворка Angular по работе с HTTP запросами.

Используем Interceptors

В некоторых случаях может потребоваться изменить запрос до того, как он попадет на сервер. Или необходимо изменить каждый ответ. Начиная с версии Angular 4.3 появился новый HttpClient. В нем добавлена возможность перехватывать запрос с помощью interceptors (Да, их наконец-то вернули только в версии 4.3!, это была одна из наиболее ожидаемых недостающих возможностей AngularJs, которые не перекочевали в Angular). Это своего рода промежуточное ПО между http-api и фактическим запросом.

Одним из распространенных вариантов использования может быть аутентификация. Чтобы получить ответ с сервера, часто нужно добавить какой-то механизм проверки подлинности в запрос. Эта задача с использованием interceptors решается достаточно просто:

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

Как видим подключение и реализация interceptors достаточно проста.

Отслеживание прогресса

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

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

Компонент с вызовом асинхронного метода¶

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

Поскольку основное назначение unit-тестов проверка не работоспособности API, а результата преобразования и (или) отображения полученных данных, то вызовы этих методов и их данных эмулируются через константы или объекты.

info-message.component.ts

info-message.component.spec.ts

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

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

Для эмуляции асинхронных методов сервисов лучше подойдут объекты.

app.service.ts


app.service.spec.ts

Объект позволяет эмулировать обращение к асинхронному методу (название). Но сам тест выполняется синхронно, внутри него не выполняется никаких асинхронных действий.

При использовании в тестах Angular компонентов сервисов следует помнить, что Angular имеет иерархическое построение injector-ов. Так, если сервис был определен на уровне компонента, то при тестировании он должен быть взят не из корневого injector-а, а из injector-а самого компонента.

Но если сервис определен именно в модуле, т. е. находится в корневом injector-е, то можно использовать более простой в восприятии способ получения сервиса с использованием .

Для асинхронности теста используйте функцию библиотеки .

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

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

info-message.component.ts

info-message.component.spec.ts

запускает тест в специальной среде исполнения. Но главное здесь — метод , возвращающий объект , который выполнится после того, как очередь задач JavaScript станет пустой, т. е. будут исполнены все асинхронные и синхронные действия. Здесь отпадает необходимость в знании точного времени исполнения.

AngularJS история

AngularJS был первоначально разработан в 2009 году Мишко Хевери и Адамом Абронсом как программное обеспечение позади сервиса хранения JSON-данных, измеряющихся мегабайтами, для облегчения разработки приложений организациями. Сервис располагался на домене «GetAngular.com» и имел нескольких зарегистрированных пользователей, прежде чем они решили отказаться от идеи бизнеса и выпустить Angular как библиотеку с открытым исходным кодом.

Абронс покинул проект, но Хевери, работающий в Google, продолжает развивать и поддерживать библиотеку с другими сотрудниками Google Игорем Минаром и Войта Джином.

Построение проекта

Последнее обновление: 15.02.2020

В прошлой теме был создан первый простейший проект.

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

В прошлой теме мы определили следующий файл angular.json:

{
  "version": 1,
  "projects": {
    "helloapp": {
      "projectType": "application",
      "root": "",
      "sourceRoot": "src",
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "dist/helloapp",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "tsconfig.json",
            "aot": true
          }
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "options": {
            "browserTarget": "helloapp:build"
          }
        }
      }
    }},
  "defaultProject": "helloapp"
}

Секция описывает конфигурацию команды построения «ng build». В частности, параметр задает каталог для компилируемых файлов приложения.

То есть мы можем ввести в командную строку команду «ng build» для компиляции проекта

C:\angular\helloapp>ng build

После выполнения этой команды в каталоге проекта появится папка dist/helloapp, где мы сможем увидеть все файлы приложения. Мы можем расположить эти файлы на любой веб-сервер и так же обращаться к главной странице приложения.

Здесь мы видим, что у нас получается много файлов, в том числе файлы с расширением .js.map, которые нам в принципе могут быть не нужны. А основные файлы имеют довольно большой размер. Тем не менее мы можем все это оптимизировать. Так, изменим файл angular.json следующим образом:

{
  "version": 1,
  "projects": {
    "helloapp": {
      "projectType": "application",
      "root": "",
      "sourceRoot": "src",
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "dist/helloapp",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "tsconfig.json",
            "aot": true
          },
          "configurations": {
            "production": {
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "namedChunks": false,
              "vendorChunk": false,
              "buildOptimizer": true
            }
          }
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "options": {
            "browserTarget": "helloapp:build"
          }
        }
      }
    }},
  "defaultProject": "helloapp"
}

Теперь мы добавили для команды build подсекцию «configurations», которая задает дополнительные конфигурации проекта. И здесь указана однак конфигурация — «production» — то есть набор настроек, которые применяются к построению приложения, когда оно уже готово к развертыванию и полноценному использованию. И здесь определяется следующий набор настроек:

  • : указывает, будет ли использоваться оптимизация

  • : указывает, будет ли в название генерируемого файла добавляться хеш-значение. Значение говорит, что в названия всех генерируемых файлов добавляется хеш

  • : определяет, будут ли генерироваться файлы sourceMap

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

  • : определяет, будет ли создаваться для сторонних используемых в приложении библиотек отдельный файл

  • : подключает пакет для оптимизации при использовании опции

Стоит отметить, что эти конфигурации никак не влияют на процесс разработки и тестирования приложения. Мы также сможем запускать проект при помощи команды «ng serve». Она не будет применять никаких оптимизаций из конфигурации «production», иначе это бы увеличило процесс перекомпиляциии приложения при каждом изменении кода.

Теперь при компиляции используем конфигурацию production. Для этого команде build надо передать флаг :

C:\angular\helloapp>ng build --prod

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

НазадВперед

NgModules¶

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

Создание компонента Angular¶


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

В начале файла определяется директива , которая импортирует функциональность модуля , предоставляя доступ к функции декоратора .

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

И в конце экспортируется класс компонента , в котором как раз определяется модель — в данном случае это пустая строка.

Организация кода

Избавляемся от громоздких выражений в

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

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

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

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

В данном примере импорт производится напрямую из вместо громоздкого выражения (например ). Это возможно благодаря ре-экспорту публичных компонентов в основном файле . Желательно создать файл на каждый пакет в котором нужно произвести реэкспорт всех публичных модулей:

Core, Shared и Feature модули

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

CoreModule

Основное предназначение CoreModule — описание сервисов, которые будут иметь один экземпляр на все приложение (т.е. реализуют паттерн синглтон). К таким часто относятся сервис авторизации или сервис для получения информации о пользователе. Пример CoreModule:

SharedModule

В данном модуле описываются простые компоненты. Эти компоненты не импортируют и не внедряют зависимости из других модулей в свои конструкторы. Они должны получать все данные через атрибуты в шаблоне компонента. не имеет никакой зависимости от остальной части нашего приложения.Это также идеальное место для импорта и реэкспорта компонентов Angular Material или других UI библиотек.

FeatureModule

Здесь можно повторить Angular style guide. Для каждой независимой функции приложения создается отдельный FeatureModule. FeatureModule должны импортировать сервисы только из . Если некоторому модулю понадобилось импортировать сервис из другого модуля, возможно, этот сервис необходимо вынести в .

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

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

  1. https://github.com/ngrx/store
  2. http://stepansuvorov.com/blog/2017/06/angular-rxjs-unsubscribe-or-not-unsubscribe/
  3. https://medium.com/@tomastrajan/6-best-practices-pro-tips-for-angular-cli-better-developer-experience-7b328bc9db81
  4. https://habr.com/post/336280/
  5. https://angular.io/docs

… и минусы

  • У NGRX, конечно, есть кривая обучения. Она не большая, но и не такая уж и маленькая, и я думаю, что это требует некоторого опыта или глубокого понимания некоторых программных паттернов. Любой разработчик со средним стажем должен быть в порядке, но для младшего поначалу может быть немного запутанным.
  • Для меня это кажется немного многословным. Но каждый раз, когда вы добавляете какое-либо свойство в состояние, вам нужно добавлять действия, диспетчеры, вам может потребоваться обновить или добавить селекторы, эффекты, если таковые имеются, обновить магазин. А также вы запускаете конвейерную (конкатенацию) rxjs операторов и наблюдаемых повсюду.
  • NGRX не является частью угловых библиотек ядра и не поддерживается Google, по крайней мере, напрямую, потому что в команде Angular есть участники ngrx. Просто нужно кое-что обдумать, прежде чем вы добавляете библиотеку, которая будет большой зависимостью для вашего приложения.

Отлично… Итак, каковы преимущества использования NGRX?

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

Angular.json¶

Для компиляции приложения мы будем использовать Angular CLI, поэтому нам надо описать поведение CLI с помощью файла . Итак, добавим в корневую папку проекта новый файл и определим в нем следующее содержимое:

Вкратце пройдемся по структуре файле. Вначале определяется параметр . Он определяет версию конфигурации проекта.

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

Проект определяет следующие опции:

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

Для каждой команды задается параметр , который определяет инструмент для построения проекта. Так, для команды «» задано значение «» — данный билдер для построения использует сборщик пакетов webpack. А для команды «» задано значение «» — данный билдер запускает веб-сервер и развертывает на нем скомпилированное приложение.

Параметр задает параметры построения файлов. Для команды «» здесь определены следующие опции:

  • : путь, по которому будет публиковаться скомпилированное приложение
  • : путь к главной странице приложения
  • : путь к главному файлу приложения, где собственно запускается приложение Angular
  • : путь к файлу полифилов
  • : путь к файлу конфигурации TypeScript
  • : указывает, будет ли использоваться компиляция AOT (Ahead-Of-Head) (предварительная компиляция перед выполнением). В данном случае значение означает, что она используется

Для команды «» указана только одна опцияя — , которая содержит ссылку на конфигурацию для команды — «». То есть по сути эта команда использует ту же конфигурацию, что и команда .

Последняя опция указывает на проект по умолчанию. В данном случае это наш единственный проект.


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

В итоге у нас получится следующая структура проекта:

AngularJS Extends HTML

AngularJS extends HTML with ng-directives.

The ng-app directive defines an AngularJS application.

The ng-model directive binds the value of HTML controls (input, select, textarea) to application data.

The ng-bind directive binds application data to the HTML view.

AngularJS Example

<!DOCTYPE html> <html><script src=»https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js»></script> <body><div ng-app=»»>   <p>Name: <input type=»text» ng-model=»name»></p>   <p ng-bind=»name»></p> </div> </body> </html>

Example explained:

AngularJS starts automatically when the web page has loaded.

The ng-app directive tells AngularJS that the <div> element is the «owner» of an AngularJS application.

The ng-model directive binds the value of the input field to the application variable name.

The ng-bind directive binds the content of the <p> element to the application variable name.

AngularJS Applications

AngularJS modules define AngularJS applications.

AngularJS controllers control AngularJS applications.

The ng-app directive defines the application, the ng-controller directive defines the controller.

AngularJS Example

<div ng-app=»myApp» ng-controller=»myCtrl»>First Name: <input type=»text» ng-model=»firstName»><br> Last Name: <input type=»text» ng-model=»lastName»><br><br> Full Name: {{firstName + » » + lastName}}</div><script> var app = angular.module(‘myApp’, []);app.controller(‘myCtrl’, function($scope) {  $scope.firstName= «John»;  $scope.lastName= «Doe»;});</script>

AngularJS modules define applications:

var app = angular.module(‘myApp’, []);

AngularJS controllers control applications:

AngularJS Controller

app.controller(‘myCtrl’, function($scope) {  $scope.firstName= «John»;  $scope.lastName= «Doe»;});

You will learn more about modules and controllers later in this tutorial.

AngularJS Expressions

AngularJS expressions are written inside double braces: {{ expression }}.

AngularJS will «output» data exactly where the expression is written:

AngularJS Example

<!DOCTYPE html> <html><script src=»https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js»></script><body><div ng-app=»»>   <p>My first expression: {{ 5 + 5 }}</p> </div> </body> </html>

AngularJS expressions bind AngularJS data to HTML the same way as the ng-bind directive.

AngularJS Example

<!DOCTYPE html> <html> <script src=»https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js»></script> <body><div ng-app=»»>  <p>Name: <input type=»text» ng-model=»name»></p>  <p>{{name}}</p></div> </body> </html>

You will learn more about expressions later in this tutorial.


С этим читают