Как мы декомпозировали Angular-компонент на 2800 строк и чему нас это научило

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

Как компонент вышел из-под контроля

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

Но требования росли. Появились зоны с drag-and-drop на базе Angular CDK, карточки с контекстно-зависимым отображением, анимации переходов и стилевые эффекты, завязанные на состояние экрана.

Через несколько спринтов компонент разросся до 1800 строк шаблона и 1000 строк логики. Шаблон оказался перегружен условной разметкой: один и тот же UI-элемент отображался по-разному в зависимости от статуса, типа данных и прав пользователя. Поиск нужного участка занимал минуты, а любое изменение в одном блоке могло задеть соседний.

Анатомия монолитного Angular-компонента

Схема 1. Анатомия монолитного компонента

Почему нельзя было просто «разрезать»

Проблема была не только в объеме кода, но и в плотной связанности логики.

Drag-and-drop был распределен по нескольким частям компонента: перемещение карточки влияло сразу на несколько зон интерфейса. Отображение зависело от состояния, типа данных и пользовательских прав. Стили, анимации и условия рендера тоже были тесно связаны с состоянием родителя.

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

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

Как мы подошли к рефакторингу

Мы начали с того, что разделили экран на смысловые блоки и для каждого блока зафиксировали три вещи:

  • какие данные он получает;
  • какие события отдаёт наружу;
  • от какого состояния родительского компонента зависит его поведение.
До и после декомпозиции Angular-компонента

Схема 2. До и после: Smart/Dumb-декомпозиция

В качестве базового подхода выбрали паттерн Smart/Dumb-компонентов. Родительский компонент оставили “умным”: в нём остались данные, координация состояния и общая логика экрана. Дочерние компоненты взяли на себя отображение, локальное взаимодействие и работу через понятный API на основе @Input() и @Output().

Рефакторинг занял 3-4 дня плотной работы. Мы переносили блоки постепенно, проверяли визуальное поведение и только потом переходили к следующей части. Чтобы не мешать параллельной разработке, несколько раз в день синхронизировались с командой и регулярно вливали изменения в основную ветку.

Что оказалось самым сложным

Сложнее всего далось разделение drag-and-drop.

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

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

Как разделили drag-and-drop между компонентами

Схема 3. Как разделили drag-and-drop между компонентами

Что получилось в итоге

Технически задача была выполнена в срок. Монолит превратился в набор читаемых и переиспользуемых компонентов с понятными контрактами.

Но вскоре после этого заказчик пересмотрел концепцию экрана. Изменилась компоновка зон, часть карточных сценариев объединили, а drag-and-drop получил другую механику взаимодействия.

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

Этот кейс оказался полезным не только как рефакторинг, но и как проверка того, насколько архитектурные решения зависят от продуктового контекста.

Даже технически правильное решение может потерять актуальность, если меняется сам экран и его сценарии.

Что мы вынесли из этого кейса

1. Плановый рефакторинг всё равно нужно соотносить с roadmap

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

2. Декомпозицию лучше делать до того, как компонент станет критически сложным

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

3. Даже если конкретная реализация не доходит до production, инженерная работа не пропадает

Подходы к разделению ответственности, построению API компонентов и инкапсуляции сложного UI-поведения затем применяются в других проектах и помогают принимать решения быстрее и точнее.

Этот кейс напомнил нам, что рефакторинг касается не только качества кода, но и контекста продукта. Именно сочетание инженерной аккуратности и понимания бизнес-изменений делает разработку по-настоящему зрелой.