В этой статье я расскажу о процессе разработки компонента Angular. Цель этой серии — показать мыслительный процесс, ведущий к разработке, и мыслительный процесс, лежащий в основе принимаемых решений. Я собираюсь заняться одним из самых печально известных стандартных компонентов, попытавшись создать свою собственную таблицу данных.

Со временем этот компонент будет добавлен в мою библиотеку Unopinionated Angular Toolbox. Цель этой библиотеки состоит в том, чтобы предоставить ряд компонентов, стилистически не имеющих мнения. Они занимаются только внутренней компоновкой и позволяют потребителю компонентов стилизовать их по своему усмотрению. Angular Material и NgPrime уже проделали хорошую работу по предоставлению самоуверенных библиотек компонентов, поэтому моя цель — вместо этого предоставить компоненты, ориентированные на функциональность и позволяющие разработчику делать все остальные выборы.

Тем временем я собираюсь разработать этот компонент в виде отдельного git-репозитория, который можно найти здесь, от остальной части UAT, чтобы любой желающий мог легко следить за процессом проектирования на разных этапах.

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

Я собираюсь использовать стандартные элементы таблицы html, чтобы таблицы были семантически правильными. Таблицы HTML имеют очень точное определение того, какие элементы могут появляться внутри них, что означает, что я не могу использовать пользовательские элементы в качестве селекторов компонентов. Вместо этого я воспользуюсь преимуществом одного из альтернативных форматов селекторов Angular, где я могу привязать свои компоненты к стандартным элементам html, которые также включают определенный атрибут. Я буду ставить перед атрибутами, которые я использую, префикс библиотеки UAT, uat, чтобы в будущем избежать конфликтов имен со стандартом HTML или любыми другими библиотеками.

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

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

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

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

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

Небольшой интерес здесь заключается в том, что я решил сделать отдельный компонент для строки заголовка вместо повторного использования существующей строки. Вместо этого я мог бы предоставить какой-то механизм переключения, чтобы сказать общему компоненту строки, отображать ли элементы ‹tr› или ‹td› внутри себя, но я решил, что будет чище иметь 2 отдельных компонента, каждый из которых имеет установленный шаблон вместо механизма переключения. .

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

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

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

Сама таблица будет принимать массив данных, представляющих строки, из которых она будет извлекать значения для рендеринга. Я мог бы сделать это просто массивом явно типизированного any, но вместо этого я решил сделать сам компонент таблицы универсальным, и общий параметр будет использоваться для определения строк данных, передаваемых в таблицу. Это, вероятно, обычно не будет полезным, поскольку нет способа установить общий тип компонента, который создается с помощью шаблона. Но я добавил общий параметр на случай, если позже разработчик захочет захватить компонент таблицы с помощью ViewChild или каким-либо другим методом. Таким образом, они могут аккуратно взаимодействовать с таблицей, не теряя информацию о типе своих данных, если захотят. Я также пропущу этот универсальный параметр через все внутренние компоненты, чтобы все они были правильно типизированы, если возникнет такая необходимость.

После реализации моделей и создания необходимых входных данных и структурных директив компонент теперь может принимать модель и массив строк данных в качестве входных данных и правильно отображать их. Я также добавил базовую документацию к компонентам и моделям. Эту версию я буду рассматривать как mvp, поскольку теперь она способна отображать простой набор данных по умолчанию. Эту версию можно найти в ветке начальной рабочей версии.

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

Выполнив все это, я сразу же обнаружил недостаток в своем дизайне. Текущая реализация полагается либо на наблюдаемую для передачи новых моделей и данных, либо на связанные ссылки, которые должны быть явно установлены для новых объектов. Это то, чего я ожидал, видя его раньше, но мне не пришло в голову заняться этим на ранней стадии разработки. Я считаю, что решение этой проблемы будет состоять в том, чтобы в конечном итоге создать пользовательскую реализацию обратного вызова обнаружения изменений ngOnChanges. Но если я это сделаю, мне придется быть осторожным, чтобы поддерживать высокую производительность и не замедлять обнаружение изменений. Я решил, что буду решать этот вопрос в будущем. Я мог бы в конечном итоге просто явно изменить дизайн, чтобы потребовать от пользователя подачи таблицы через наблюдаемые или новые объекты, но я еще не принял такого решения.

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