Недавно наша команда выпустила стабильную версию Nebular. Nebular - это библиотека Angular, которая упрощает разработку сложных приложений с богатым пользовательским интерфейсом. Он состоит из следующих модулей: Тема, Аутентификация и Безопасность.
Во время работы над несколькими выпусками-кандидатами мы столкнулись с некоторыми проблемами, и я хочу рассказать вам о самых интересных из них.
Почему мы создали Nebular с Angular CDK
Как вы знаете, гораздо проще создавать пользовательские интерфейсы с использованием библиотек компонентов. Одной из важных частей этих библиотек являются плавающие компоненты - компоненты, которые визуализируются поверх других компонентов и могут перекрывать их, например диалоги, контекстные меню, всплывающие подсказки и т. Д. Библиотека Nebular не является исключением и требует наличия таких компонентов.
Первым, что было решено создать, был компонент Popover. Идея этого компонента состоит в том, чтобы отображать контент в области вокруг основного элемента. Вот изображение для иллюстрации:
Мы решили создать его как директиву Angular, которая принимает контент как @Input
, примерно так:
Результат оказался весьма успешным, и в ходе наших экспериментов мы также обнаружили возможность использовать его в качестве основы для других компонентов, таких как контекстное меню, всплывающая подсказка и так далее. Чтобы дать вам некоторые технические подробности, мы реализовали позиционирование с помощью собственного JavaScript API, или другими словами - element.getBoundingClientRect()
. Это позволило нам рассчитать местоположение Popover на основе положения хост-элемента. Еще одна интересная функция, которую мы добавили, - это AdjustmentStrategy
. Ключевым моментом стратегии является предоставление возможности перемещать положение Popover в зависимости от доступного пространства вокруг хост-элемента. Например, если мы поместили его наверху, а в области просмотра недостаточно места для его рендеринга, AdjustmentStrategy
попытается отобразить его под хостом.
Мы остались довольны результатами и начали создавать остальные компоненты на основе нашей реализации Popover. На первых этапах это было нормально, но, продвигаясь дальше, мы выяснили, что у него есть некоторые ограничения, о которых мы не думали изначально. Он был хорош для рендеринга плавающих панелей вокруг основного элемента, таких как всплывающие подсказки и контекстные меню, но, к сожалению, он не был настолько успешным для рендеринга глобальных, таких как тосты и диалоги. На этом этапе мы пришли к пониманию, что требуемое решение является обширным, а его реализация потребует много времени. Вот почему мы начали искать сторонние альтернативы, а не создавать его с нуля.
Угловая интеграция с CDK
Мы знали, что Angular предоставляет комплект для разработки компонентов (CDK), поэтому, очевидно, мы решили сначала поискать его. Оказывается, в нем много основных понятий, которые очень полезны для наших случаев. Позвольте мне объяснить, кто из них помог нам больше всего.
Рендеринг компонентов Angular динамически поверх других компонентов
Вы когда-нибудь сталкивались с ситуацией, когда выпадающий список обрезается каким-то родительским элементом с overflow: hidden
? Что ж, это довольно распространенная проблема, которую мы можем решить, только отрисовав компонент где-нибудь в верхней части дерева документа, например сразу после открывающего тега <body>
, и позиционируя элемент как абсолютный.
Это аналогичная проблема, которую мы пытались решить здесь, в Небуларе. Сначала мы реализовали нестандартное решение. У Nebular есть корневой компонент, который охватывает все приложение. Этот корневой компонент имеет возможность динамически отображать компоненты перед остальным содержимым, используя ComponentFactoryResolver
, предоставляемый базовым модулем Angular.
Но, установив CDK, мы смогли решить эту проблему гораздо более элегантно, используя концепцию Portals & Overlays. Давайте посмотрим.
Portal
- это тип Component
или TemplateRef
, который может динамически отображаться в одном из предопределенных слотов на странице с именем PortalOutlets
. Вот простой пример:
Overlay
, в свою очередь, является реализацией PortalOutlet
. Ключевым моментом является то, что он отображает предоставленное Portal
как плавающее содержимое где-то на странице. Кроме того, он поддерживает стратегии позиционирования, чтобы мы могли описать, как контент размещается в документе.
Используя эту концепцию, мы можем создать компонент очень просто . Вот небольшой пример всплывающей подсказки:
Реализация выглядит довольно простой. Мы просто внедряем наложение CDK, создаем его экземпляр, создаем портал с нашим настраиваемым компонентом и присоединяем его к ранее созданной ссылке наложения. В конце мы устанавливаем содержимое экземпляра всплывающей подсказки, передавая строку в созданный компонент. Использование директивы всплывающей подсказки будет следующим:
Последнее, что осталось подготовить для нашей всплывающей подсказки, - это добавить код, который будет показывать / скрывать всплывающую подсказку в качестве реакции на взаимодействие с пользователем. И здесь в игру вступают триггеры наложения.
Показывать и скрывать динамический контент на основе взаимодействия с пользователем
Задача отображения и скрытия плавающих элементов не выглядит такой сложной. Даже в этом случае нам необходимо создать абстракцию, чтобы обеспечить возможность легкой адаптации ко всем компонентам. CDK не имеет такой функциональности, однако мы смогли легко расширить ее с помощью нашего собственного TriggerStrategy
понятия. Идея TriggerStrategy
состоит в том, чтобы иметь возможность подписаться на события отображения и скрытия, но события будут запускаться по-разному в зависимости от реализации стратегии - hover
, click
, focus
и т. Д. Интерфейс довольно прост:
Для наших случаев использования мы разработали следующие реализации: ClickTriggerStrategy
, HintTriggerStrategy
и FocusTriggerStrategy
.
Давайте посмотрим на реализацию HintTriggerStrategy
:
Событие Show будет запущено, если пользователь начнет наводить указатель мыши на элемент хоста, а hide будет запущен при перемещении мыши из элемента хоста. Как это просто. Остальные триггерные стратегии реализованы аналогично. Плавающие компоненты просто регистрируют требуемую стратегию триггера и показывают / скрывают как реакцию на триггерные события.
Перемещение и захват фокуса браузера между компонентами Angular
Доступность - жизненно важная часть любого современного веб-приложения. И Небулар не исключение. Во время разработки компонента Dialog мы столкнулись со следующими проблемами доступности: когда пользователь открывает диалоговое окно, щелкнув какую-либо кнопку, фокус браузера остается на кнопке. Это означает, что если вы нажмете пробел несколько раз, вы создадите больше диалогов. Это определенно нехорошо. Когда ваша операционная система показывает уведомление о подтверждении, вы просто нажимаете клавишу ввода, чтобы подтвердить его. Вам не нужно перемещать мышь или использовать вкладку, чтобы сфокусировать кнопку OK. То же самое следует применить к диалогу. Фокус должен быть перемещен на первый фокусируемый элемент в созданном диалоге.
Здесь пригодился модуль доступности Angular CDK. Он поставляется с помощником FocusTrap
, который обеспечивает возможность захватывать фокус внутри элемента. Именно то, что нам нужно, с компонентом Dialog. Его использование довольно просто:
Вот и все! После рендеринга DialogComponent
первый фокусируемый элемент внутри него получит фокус. Но здесь у нас есть другая проблема - мы должны восстановить выбранный элемент после закрытия диалога. Эта функция не является частью Angular CDK, поэтому мы расширили ее и сами реализовали функцию восстановления фокуса:
И его использование становится следующим:
Как видите, теперь стало довольно легко захватить фокус внутри компонента и затем освободить его, когда компонент был уничтожен. Такое поведение требуется для интерактивных компонентов, которые могут появляться на странице, а затем исчезать после взаимодействия с пользователем.
Обработка прокрутки и размеров встроенного приложения
Одна из особенностей Nebular - это возможность отображать в другом приложении как встроенное приложение. Например, как часть другого приложения Angular или даже не Angular. Вот почему обработка прокрутки и измерение приложений должны выполняться на уровне контейнера приложения, а не на уровне окна. К сожалению, такие сервисы Angular CDK, как ScrollDispatcher
и ViewportRuler
, измеряют все на уровне окна. К счастью, нет проблем с предоставлением пользовательских реализаций, которые в нашем случае оказались очень полезными.
Например, адаптер ScrollDispatcher
выглядит так:
А потом предоставил вот так:
Как видите, Angular CDK имеет неплохую расширяемость благодаря внедрению зависимостей.
Заключение
Подводя итог, я должен сказать, что интеграция Angular CDK была очень хорошей инвестицией. Это сэкономило нам значительное количество времени и дало нам возможность перестать изобретать колесо и полностью сосредоточиться на разработке компонентов и специфичных для Nebular функций. Более того, Angular CDK достаточно легко расширяется, а это значит, что мы можем адаптировать его под свои нужды.
Полезные ссылки
- Ознакомьтесь с Angular CDK https://material.angular.io/cdk
- Документация по туманностям https://akveo.github.io/nebular
- Подпишитесь на блог Akveo, чтобы быть в курсе https://medium.com/akveo-engineering
- Следуйте за мной в Twitter: https://twitter.com/NikPoltoratsky