![image](https://habrastorage.org/web/c4c/f2a/e45/c4cf2ae45ddc46719ec1d002dee0c33e.jpg)
В своей статье я хочу поделиться с вами опытом использования нового Angular как основы для наших enterprise приложений. Речи о том, что новый Angular лучше, чем React, Vue или какая-то другая популярная сейчас библиотека, в статье не пойдет, хотя, конечно, я буду сравнивать его с конкурентами. Все решения имеют свои плюсы и минусы, и то, что хорошо подошло одному проекту, может устроить сущий ад в другом. Итак, прежде чем объяснить, чем нас зацепил новый Аngular, расскажу немного о том, что мы уже используем в разработке.
Наш основной проект имеет долгий путь развития и построен на уже устаревших технологиях — Marionette + Backbone + Coffescript. Пару лет назад мы поняли, что развивать проект в таком стеке стало довольно тяжело, и начали изучать альтернативы в экосистеме фронтенда и думать, как же нам мигрировать туда нашего «зверя».
Начиная работу над проектом, мы выбрали популярный сейчас стек React + Redux + [Reselect, Redux-Saga, Redux-form, etc]. Это была наша первая попытка выбора нового стека и, к сожалению, неудачная — он показался нам не столько удобным, как Marionette много лет назад. В результате исследований около полугода назад наш выбор пал на новый Аngular и мы им довольны. Пока новый Angular не используется на основном проекте компании, но в перспективе мы не видим препятствий для его переноса на это решение (ну разве что нас останавливает огромная кодовая база на Coffeescript). Итак, давайте посмотрим, что же мы нашли в новом Angular и чем он нам понравился.
Пункты будут излагаться в произвольном порядке, а не по степени значимости. Начнем, конечно же, с тех вещей, которые считаем преимуществами решения, а в конце поговорим и про недостатки.
Плюсы
Angular — это фреймворк
Это один из самых важных для нас плюсов. Когда пять лет назад я начинал проект в компании, то думал, что будет легко построить классный и целостный продукт. Как же я был наивен! :) Чем больше народу подключалось в проект, тем больше времени я тратил на приведение целого «зоопарка» решений к общему стилю и архитектуре. Отчасти это было вызвано той свободой, что давал нам Marionette, ведь это был лишь набор примитивов, который никак не ограничивал разработчика.
Все люди разные и каждый будет строить код своего кусочка продукта так как ему нравится. Как я только не боролся с данной проблемой — вводил coding style, ревьювил код, делал описание архитектуры, но все равно периодически сталкивался с нестандартным решением от одного из своих коллег, и приходилось тратить много усилий, чтобы вернуть проект к однотипному развитию и архитектуре.
Потом было время разработки на React и, казалось бы, Redux и Flux пришли мне на помощь. Да, это позволило больше не беспокоиться, что изменение данных моделей будет разбросано по коду, но одна из проблем все же осталась. Мне приходилось следить за всем набором библиотек, которые тащились в продукт. Наш стек просто пестрил своей разнообразностью и подходами.
Потом мы перешли на Angular. Несмотря на всю его гибкость, это все-таки довольно ограничивающий разработчика фреймворк. А так как он содержит в себе довольно много возможностей, то привнесение новой библиотеки в проект стало для нас довольно редким событием, которое было проще контролировать. Наличие же стандарта кодирования, предоставляемого командой Angular, позволило мне окончательно выдохнуть и расслабится, и больше времени уделять разработке.
Конечно, в Angular все равно можно устроить хаос, но это сделать гораздо сложнее, так как неверно построенный код тут же всплывает либо при оценке производительности, либо при попытках его переиспользовать. Я был бы рад наличию более жесткого фреймворка, но, боюсь, тогда бы мы потеряли возможность его расширения, а мне бы этого не хотелось. Как мне кажется, в Angular мы нашли золотую середину — это и не позволяющий все Marionette, но и не жесткий Extjs.
Наличие cli системы
Это, конечно, далеко не killer-фича ангуляра, да и у React тоже есть свой cli (react-starter-kit), но тем не менее, начать изучение нового решения гораздо проще, используя консольный клиент, а не тратя часы на выбор boilerplate и поиск причин почему что-то не работает. Кроме этого, у консольного клиента есть огромный плюс в том, что он сразу генерирует минимальный шаблон приложения, позволяет легко запускать код как в режиме разработки или в production, так и запускать тесты.
Все это, безусловно, снижает порог входа и позволяет сразу начать писать бизнес-код, не раздумывая (до определенного момента) о расположении файлов или о написании "скелета" приложения. Также надо отдать должное ребятам из Angular — их консольный клиент построен поверх клиента emberjs. Когда-то давно я сталкивался с ember и был приятно удивлен удобством их консольного клиента, правда, это было единственное преимущество ember, отмеченное мной на тот момент.
Typescript
Когда Typescript только появился, мы не понимали его предназначение и считали, что проверка типов — это все «от лукавого» и у нас не так много дефектов связано с неверной передачей данных. Со временем мы изменили мнение и даже попытались внедрить в готовый проект на React пакет flow, но после недели мучений сдались и решили, что сможем жить и без него.
Затем обратили внимание на Angular, который полностью написан на Typescript, и подумали, а почему бы не попробовать поработать с ним снова. Первое, что мы оценили после перехода на Typescript, это возможность легкого использования es6 без ненужной возни с настройками babel и других транспилеров (и уверенность, что очередное обновление не сломает вам сборку кода). Вы просто пишете код, а компилятор без проблем преобразует их в es5.
Но больше всего нас порадовало то, что писать код в IDE стало намного удобнее и приятнее — правильно работает автодополнение, можно посмотреть документацию метода, не покидая контекста кода, код стал самодокументированным. Нам больше не надо было описывать в комментариях, что за объект мы передаем в метод, это обеспечивал за нас Typescript.
Чуть позднее мы оценили всю прелесть интерфейсов и декораторов. Да, конечно, можно писать на Typescript, используя React, поэтому это не уникальный плюс для Angular, но, когда сам фреймворк написан на Typescript — это дает вам бесшовную интеграцию вашего кода.
Компонентный подход
Почти все текущие решения используют парадигму компонентного подхода и Angular не первый, кто предлагает такое решение. Но если сравнивать подход Angular с подходами других решений, есть ряд отличий:
Вы можете не только создавать новые теги для html-разметки, но и изменять существующие. Далеко не всегда при использовании React нам удавалось сделать чистую разметку без лишних html-элементов в иерархии и это ужасно раздражало нашего верстальщика. В Angular же вы можете изменить поведение существующего html-тега через атрибут, и это делает нашу разметку чище и приятнее.
Вы можете вынести верстку в отдельный файл. В нашей компании JS-разработчики не очень любят верстать html, поэтому у нас есть верстальщик. Когда он занимался версткой React-кода без знания JS и es6, для него это было сродни хождению по минному полю. Я знаю, что в React есть возможность вынести разметку, но когда ваш код генерируется множеством JS-функций, это не так-то просто. Оказалось, что проще объяснить верстальщику на какие вещи ему не надо обращать внимание (речь о директивах, конечно же), чем научить его JS. Использование Pipe позволило нам скрыть от верстальщика всю сложную магию обработки данных, оставив только небольшое вкрапление директив.
Вы можете настраивать поведения компонента, меняя параметры работы Change Detector или работы компонента с CSS-классами. Причем при наследовании от базовых компонентов все ваши настройки сохранятся. Таким образом, можно одним параметром включить работу change detector в режим сравнения immmutable-объектов, и при наследовании от такого базового компонента избежать лишних циклов change detector механизма. Конечно, это требует от разработчика понимания, что он делает, когда наследуется от базового класса. Инкапсуляция стилей позволила нам избежать мучений с выбором названия для очередного класса selected внутри одного из многих компонентов.
- Можно указать какие параметры могут приходить компоненту на вход и какие события могут от него исходить. После этого, при попытке передать лишний параметр на вход компонента, компилятор даст вам знать, что компонент ничего не знает о таком параметре. Это помогает избежать ошибок при использовании чужих компонентов. Конечно, в React есть PropTypes, которые также позволяют контролировать то, что попадает на вход вашего компонента, так что Angular тут не уникален.
RxJS
Angular построен поверх библиотеки RxJS и странно было не использовать её. Но когда мы первый раз столкнулись с этой библиотекой, то пришли в ужас. Нам потребовалось несколько дней, чтобы понять, как правильно написать цепочку обработки наших данных. В результате получился код, который может прочитать только тот, кто хоть раз пробовал что-то писать на этой библиотеке. Нас настолько это пугало, что мы даже подумывали отказаться от использования RxJS.
Прошло немного времени, мы несколько раз перечитали документацию, попробовали библиотеку в действии и привыкли. Библиотека RxJS позволяет мне творить очень крутые вещи с обработкой данных и почти всегда мой код работает так, как я и задумал. Правда, даже сейчас, по прошествии полугода я понимаю, что не знаю полностью всей мощи этой библиотеки. Но зато я понимаю, что вряд ли откажусь от её использования.
Конечно, RxJs можно встроить и в React, но, повторюсь, когда ваш фреймворк использует то же, что и вы, можно легко связывать свой код с фреймворком, например, используя HTTP-модуль Angular для работы с backend’ом.
Dependency Injection
Надо начать с того, что Dependency Injection используется Angular почти везде, значит, это дает возможность изменять поведение фреймворка в очень больших границах: можно вклиниваться в определенные процессы работы, например, сконструировать роуты на старте продукта, можно полностью заменять некоторые части продукта своими.
Кроме этого, DI в Angular настолько гибкий, что позволяет реализовывать на нем многие хорошо известные паттерны, такие как Singleton, Factory, Facade и т.д. Мы используем DI повсеместно, начиная от взаимодействия компонентов, заканчивая созданием сервисов для показа модальных окон или нотификаций пользователю. И конечно, вся работа с backend’ом построена через сервисы и DI.
Я не являюсь приверженцем redux-подхода, так как имею негативный опыт работы с ним – (в одном из проектов я устал от огромного количества шаблонного кода, нередко больших redux saga обработчиках и тормозящих reselect геттеров). Возможно, мы его неправильно использовали, но как мне кажется, дело было не в этом. Мы встраивали весь этот стек в готовый проект с его интерфейсами, а не строили новый, затачивая работу с backend’ом для удобной укладки всего в стейт и side-effect обработчики.
Но вернемся к Angular. Если у вас нет необходимости в redux-подходе, но вы хотите иметь так называемый «single state of true», то можете использовать сервисы, RxJS-компоненты и Uniderectial data flow, поддерживаемый в Angular. Код получится гораздо компактнее и будет не хуже redux-подхода. Со временем вы сможете трансформировать его и в redux-подход, почти не меняя код. Как будет выглядеть такая архитектура в конце, можно посмотреть тут.
Модули
Впервые с модулями я познакомился в Marionettejs и это было безумно удобно. Я разбивал модуль на множество файлов — контроллеры отдельно, вьюхи отдельно и т.д. А после Marionette собирал за меня все вместе.
Занимаясь разработкой проекта на React, я часто вспоминал про модули, но из коробки React ничего похожего не предлагает, а искать или реализовывать что-то подобное не было времени. Angular же, напротив, дает вам понятие модуля с самого начала. И они здорово упрощают совместную работу с кодовой базой. Вы просто собираете все кусочки вашего бизнес-компонента в модуль, и для его использования другим разработчиком, достаточно просто добавить в код ваш модуль.
Так как модули имеют собственное пространство имен для компонентов, вы можете не беспокоиться о том, что ваши «внутренности» будут доступны кому-то во внешнем коде. Это отличный слой абстракции от остального кода. И конечно, у вас есть возможность экспортировать нужные компоненты наружу, чтобы их могли использовать другие разработчики.
Кроме этого, модули — отличный инструмент для документирования кода. Конечно, есть особенности использования модулей и о них надо знать. Первая особенность заключается в том, что сервисы, определенные в модуле, доступны всему приложению, и надо быть аккуратней с именованием этих сервисов. Angular предлагает решение в виде использования аналога Symbol типа из нового стандарта JS, чтобы избежать коллизий.
Вторая особенность скрыта в lazy load-модулях и заключается в том, что сервисы, подгружаемые из таких модулей, доступны только внутри модуля. Это довольно неприятная особенность, которая заставила нас искать обходное решение, так как наш продукт построен по плагинной системе и нам необходим беспрепятственный доступ к сервисам. Эту особенность мы исключили благодаря ранней предзагрузке сервисов на старте приложения.
Развитие фреймворка и его поддержка
Angular, в отличие от React, развивается всего одним мейтенером, гуглом, и для нас это имеет важное значение. Из нашего опыта работы с React, развитием стека которого, по сути, занимается комьюнити, мы пару раз попадались на том, что выбранные библиотеки переставали поддерживаться или имели очень низкий темп исправления дефектов, а это тормозило нашу разработку.
Пару раз нам приходилось менять одни библиотеки на другие, и много раз нам приходилось форкать библиотеки и вносить свои правки. Да, мы не застрахованы от подобного и с Angular, но тут сторонних библиотек у нас гораздо меньше в силу того, что фреймворк содержит многое в себе. Что же касается дефектов именно в самом Angular, то при острой необходимости, можно воспользоваться платной поддержкой от команды Angular, которая поможет решить проблему или найти другое решение. Но, конечно, на это придется потратить немного бюджета.
Это далеко не все плюсы, которые есть в Angular, а только те, которые показались нам важными. Кроме этого, в Angular есть возможность перевести код в WebWorker-ы, что сделает его многопоточным, Server Side Rendering и многое другое. Сейчас у нас нет необходимости в этих возможностях, но приятно что если они нам понадобятся, то мы в любой момент сможем ими воспользоваться, не сильно меняя свой код.
Минусы
Как и любое другое решение, Angular далеко не идеальный продукт и, конечно же, содержит минусы.
Довольно посредственная документация
Документация есть, но её качество оставляет желать лучшего. Нередко открыв документацию, видно просто название методов класса, без какого-либо описания, что они делают и как. Даже если описание будет присутствовать, то не всегда без живого примера можно понять, как использовать тот или иной метод.
Кто пробовал создавать компоненты в Angular динамически, меня поймут. Некоторые вещи просто отсутствуют в документации, и приходится лезть в код, чтобы понять, как достигнуть цели. Но я думаю, со временем данная проблема уйдет, и ребята допишут документацию.
Большой объем результирующего кода
Если использовать Angular из коробки, то на выходе мы получаем здоровенные модули, от которых наступает тоска и печаль. Но если немного покопаться в документации, то можно найти ряд решений, которые существенно уменьшат размер кода на выходе. И тут есть несколько подходов:
Исключить те модули, которые не используете. Например, если не используете анимацию, которую предлагает Angular, или формы, то не нужно и тащить эти пакеты с собой. Также можно использовать tree-shaking при сборке проекта, что исключит из кода неиспользуемые части. Например, это сильно уменьшит размер RxJS, который вы включаете в production код.
Использовать AOT-компиляцию, что исключит runtime-компилятор из вашего кода, а он немало весит. Правда, это возможно только если не генерировать компоненты динамически, как это делаем мы. Но, даже в этом случае, компилятор можно вынести в отдельный модуль и загружать его отдельно.
- Использовать Lazy loading-модули. Роутер Angular умеет работать с lazy loading-модулями из коробки и даже позволяет выбрать стратегию загрузки модулей или написать свою. По умолчанию модули грузятся только при первом обращении к ним, но можно задать подгрузку модулей в бэкграунде, что ускорит переход в подгружаемые модулями разделы.
Для нас данный минус не особо критичен, так как наше приложение релизится раз в полгода, огромной нагрузки на него нет, и нас спасет браузерный кэш. И мы не предполагаем, что его кто-то станет использовать с мобильных девайсов, для этого лучше сделать отдельное native-приложение.
Сложный порог входа
Angular очень сложный фреймворк. С одной стороны, используя cli, можно легко начать на нем писать код, но, с другой стороны, не изучив документацию, к примеру, как работает change detector, можно написать очень неоптимальный код или даже неработающий вовсе.
Мы тоже не обошли стороной данный минус и довольно много времени убили на попытки понять, почему сервис из lazy load-модуля не виден в другом модуле. Кроме самой сложности Angular, много времени у нас отняла библиотека RxJs, в которой не так просто разобраться, но оно того стоит.
Заключение
Исходя из сказанного, мы пришли к выводу, что Angular стал действительно корпоративным фреймворком, на котором можно не бояться строить большие приложения. В основном это произошло благодаря осмыслению опыта с первой версией Angular, цепочки правильно принятых решений, например, выбор RxJs и Typescript, а также целостности сделанного решения.
React же для нас — это сборник хороших идей, но из-за отсутствия многих функций из коробки, зачастую каждый лепит из него своего «монстра» из возможностей, предоставляемых сообществом. В итоге время вхождения нового разработчика в сложный проект куда большее, чем у Angular, а нередко проблемы возникают даже у соседних команд. Но, повторюсь, это сугубо наше мнение, выведенное из нашего опыта и созданных проектов. Выбирая Angular, нам важна скорость нашей разработки и то, насколько быстро наши разработчики смогут вносить изменения в код, двигая продукт вперед.