Перевод: Developing a tabs-component in Angular 2

Автор оригинала: Pascal Precht updated on Aug 11, 2016

В этом статье мы расскажем как создать широко используемый компонент в большинстве веб приложений: Вкладки (Tabs). Создание вкладок всегда было хорошим примером объяснения контроллеров директив в Angular. У Angular 2 нет концепции контроллеров директив, так как компоненты сами по себе имеют исполняемый контекст. Это так же делает намного проще обмен данными между директивами и компонентами через внедряемые зависимости. Однако, если вам нужно узнать больше о использование контроллеров в директивах в Angular 1 (к примеру что бы провести миграцию к Angular 2 ).

Сразу хотите увидеть результат?

Посмотреть Демо

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

Как это должно выглядеть?

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

Cам же процесс создания вкладки из HTML будет скрыт за декларативными элементами известными как директивы. Такой инструмент как Angular (а также Web Components) позволяет нам создать кастомные элементы, таким образом что бы любой пользователь этих элементов мог добавлять их в любые свои приложения. Вот HTML синтаксис который будет создавать вкладки:

Элементы <tab> представляет одну вкладку у которой есть описание (tabTitle) а элемент <tabs> будет делать элементы <tab> вкладками.

Если вы знаете о архитектурной концепции Angular 2, вы возможно уже поняли что, в Angular 2, можно самим определять как передавать параметры в компоненты. В то время как в Angular 1, директивы определяли как именно значение параметров должно передаваться во внутренний контекст.

Это означает что атрибут tabTitle, может быть как атрибут компонента (если он существует), так и свойство компонента. Последнее позволило бы передавать в компонент выражение. Далее пример как это могло бы выглядеть:

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

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

Начнем с создания статической версии элемента <tabs>. Если вы читали статью создания быстрых компонентов в Angular 2,
то наверно уже знаете что нам нужен декоратор @Component() что бы определить селектор и шаблон нашего компонента.

Когда мы используем декоратор Component, мы можем определить шаблон используя свойства template. Синтаксис с обратными кавычками пришел от ES2015 и позволяет определять многострочные строки без использования оператора конкатенации (сложения) строк, такого как +.

Как вы можете видеть шаблон компонента уже содержит список. Позже этот список будет заменен директивами, а пока мы делаем это для простоты объяснения. Нам так же нужно место где будет находиться содержимое вкладок. Добавим место вставки контента в наш шаблон. Это спроецирует внешний DOM в Shadow DOM (Emulation).

Супер, мы можем начать использовать наш компонент <tabs> и написать HTML:

Конечно же мы хотим использовать <tab> элементы внутри нашего компонента <tabs>, поэтому создадим новый компонент. Очевидно что элемент <tab> является примитивом. Это простой контейнерный элемент который имеет точку вставки в проекцию DOM. На также нужно будет сконфигурировать атрибут tabTitle.

Мы назвали новый компонент tab. Мы определили входящий атрибут tabTitle. И мы определили шаблон с точкой ставки контекста.

И это все? Почти. Еще немного осталось поправить, но уже сейчас мы можем попробовать использовать новый компонент <tab> внутри компонента <tabs>.

Если посмотреть на результат в браузере, то заметим что будет список из вкладок Tab 1 и Tab 2 а так же отобразиться контент обоих вкладок. Это не совсем то что мы ожидаем от вкладок, верно?

Делаем компоненты динамическими

С начало сделаем так что бы компонент <tabs> использовал динамические заголовки (title) вместо статического текста. Для генерации динамического списка вкладок нам нужна будет коллекция для его хранения.

Хорошо, но как нам получить описания вкладок для этой коллекции? Это для чего, в Angular 1, мы могли бы использовать контроллеры директив. Однако, в Angular 2 все намного проще. Во первых нам будет нужен внешний интерфейс через которых мы могли бы добавить во внутренюю коллекцию. Давай те добавим метод addTab(tab: Tab), который принимает объект Tab.

Далее нам нужно обновить шаблон что бы формировать динамический список на основе нашей коллекции. В Angular 1 нам нужно было бы использовать директиву ngRepeat. В Angular 2 имеется аналог этой директивы ngFor.

Хорошо, у нас есть коллекция и у нас есть API добавлять элементы к ней и у нас есть список который динамически генерируется в шаблоне на основе внутреней коллекции. Так как коллекция пустая по умолчанию то и генерируемый список вкладок пустой. Нам нужно буде воспользоваться методом addTab()!

Для этого нам снова понадобиться компонент <tab>. Внутри компонента мы можем получить родителя компонент Tabs используя новую более мощную систему внедряемых зависимостей.

Подождите, что же здесь произошло? tabs: Tabs это Typescript объявления типа который Angular 2 использует для механизма Внедрения зависимостей (Dependency Injection).

Если хотет почитать об этом побольше см. Angular 2 DI

tl;dr

Angular 2 Hierarchical Injector знает, что мы хотим вначале получить экземпляр Tabs, если его не будет он продолжит искать вверх по иерархии. В нашем случае начнет с компонента <tab>. Injector внутри tab запрашивает Tabs, если его не найдет, Injector запросит в родительском Injector компоненте Tabs. В нашем случае родительский Injector <tabs> и есть то что нужно. Там он получит экземпляр Tabs, и вернет его.

Сейчас важно просто понять что это объявление типа и есть механика предоставления доступа к родительскому зависимому компоненту, который в нашем случае <tabs>.
Используя этот экземпляр, мы можем просто вызывать метод addTab() с объектом Tab.

Делаем вкладки

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

Во первых нам нужен атрибут который будет активировать и деактивировать вкладку. Мы можем просто расширить наш <tab> шаблон следующим образом:

Если вкладка не активна, мы просто ее скрываем. Мы пока еще не инициализировали этот атрибут, поэтому пока его значение undefined что равносильно состоянию false. Поэтому каждая вкладка сейчас деактивирована. Для того что бы активировать хотя бы одну вкладку, нам нужно расширить метод addTab(). Следующий код для примера, активирует первую вкладку при добавление в коллекцию.

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

Затем нам нужно добавить этот метод в шаблон.

Что здесь происходит, как только активируется событие click выполнится метод selectTab().

Здесь полный исходник кода.

Так же не стоит забывать что все используемые компоненты нужно объявлять в нашем корневом модуле приложения:

 

Что же дальше?

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

Бонус

В Angular 2 часто существует несколько путей реализации одного и того же!

Попробуем реализовать наши вкладки совершенно другим путем (которым было бы достаточно сложно пойти в Angular 1 ), воспользуемся специальным свойством в Angular 2 @ContentChildren с типом QueryList и интерфейсом жизненного цикла AfterContentInit.

Более подробно о данной концепции вы можете почитать Juri Strumpflohner в его статье.

 

Если вам интересно посмотрите наши демо:

Демо

It's only fair to share...Share on FacebookShare on Google+Tweet about this on TwitterShare on LinkedInShare on VK