Недавно наша команда выпустила стабильную версию 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 достаточно легко расширяется, а это значит, что мы можем адаптировать его под свои нужды.

Полезные ссылки