Миграция с одного фреймворка на другой, например, с Angular на React, — задача, с которой сталкиваются многие команды. Причины могут быть разными: устаревший стек, проблемы с поддержкой, нехватка специалистов на рынке или потребность в более современных инструментах. Так или иначе, в какой-то момент становится очевидно: продолжать развивать проект на старом фреймворке становится дороже и рискованнее, чем перенести его на новый. 

Меня зовут Александр Марченко. Я руководитель команды Frontend-разработки в ОК. В этой статье я расскажу о особенностях и способах миграции Angular приложения на React, а также поделюсь своим опытом.

Немного контекста: знакомство с Angular и React

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

Структура Angular и React

Дисклеймер: Приведенные ниже схемы условны, а примеры кода упрощены. При сравнении двух технологий я буду делать акцент только на аспектах, непосредственно влияющих на миграцию. Также я сознательно опускаю подробности про Virtual DOM и Change Detection, так как для миграции с Angular на React это не критично.

Angular можно условно разбить на несколько ключевых частей:

  • компоненты;

  • навигация (Routing);

  • DI — инъекция зависимостей;

  • HTTP-клиент.

В типичном Angular-приложении есть точка входа (app.component), конфигурация роутинга и DI как способ управления состоянием. Для работы с HTTP используется встроенный HttpClient на базе RxJS, формы обрабатываются через Reactive Forms, а поток данных реализован через two-way binding. Шаблоны строятся на модифицированном HTML.

React, с другой стороны, проще по функционалу:

  • компоненты;

  • Context API;

  • JSX.

React сам по себе лёгок, но для полноценного приложения обычно нужны дополнительные библиотеки для роутинга, работы с состоянием, форм и HTTP.

Общее сравнение

Angular

React

Комментарии

Компоненты

Компоненты

В обоих фреймворках основная единица UI — компонент.

Навигация (Routing)

React Router (доп. библиотека)

В React роутинг нужно подключать отдельно.

DI (Dependency Injection)

Context API / сторонние библиотеки

В Angular DI встроен, в React для глобального состояния чаще использует Context или Redux.

HttpClient (RxJS)

fetch / Axios (доп. библиотека)

Angular предлагает встроенный клиент, в React — сторонние решения.

Two-way binding

Односторонний поток данных

React управляет состоянием через props и state, без автоматического двустороннего биндинга.

Reactive Forms

Формы через сторонние библиотеки

Angular имеет мощный встроенный механизм, в React часто использует Formik или React Hook Form.

Шаблоны на модифицированном HTML

JSX

В React шаблон и логика объединены в JSX.

Angular поставляется со всем необходимым из коробки: от сборщика проекта до HTTP-клиента и навигации. В случае React необходимо собрать недостающие части самостоятельно из сторонних библиотек.

Варианты миграции с Angular на React

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

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

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

Можно выделить три принципиальных способа:

  • Переписывание с нуля: полностью переписываем с нуля проект на React.

  • Пошаговая миграция: поддерживаем два приложения с редиректом друг на друга.

  • Гибрид: внедряем React в существующий проект на Angular.

Каждый из подходов имеет право на существование. Выбор зависит от здравого смысла и конкретных технических условий.

  • Для небольших приложений с минимальной кодовой базой самым простым и очевидным решением становится написание проекта «с нуля». Временные затраты здесь относительно невелики, а риски — минимальны. Но чем больше приложение, тем выше цена такого выбора: миграция займёт много времени, а новые фичи придётся поддерживать параллельно и в старом Angular-коде, и в новом React-приложении.

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

  • Гибридный подход на первый взгляд кажется самым сложным, ведь нужно «подружить» Angular и React в одном приложении, решить вопросы роутинга, стейта и постепенно избавиться от Angular. Однако именно он открывает наибольшие возможности: миграция превращается из рискованного «большого прыжка» в контролируемый процесс. То есть, можно параллельно развивать новое приложение, не замораживая старое, и переносить функционал настолько малыми шагами, насколько это удобно.

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

Именно поэтому в рамках статьи будем рассматривать алгоритм построения гибрида Angular + React.

Примечание: Важно понимать, что гибридный подход — не «серебряная пуля», поскольку универсального варианта нет. При выборе подхода надо ориентироваться на размер проекта, состояние кодовой базы и бизнес-задачи.

От теории к практике: запускаем React в Angular

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

Основная идея гибрида — запустить React внутри Angular. Но как это сделать? В целом, это несложно. 

Прежде всего необходимо установить react, react-dom, а также в tsconfig достаточно добавить в “compilerOptions” свойство "jsx"

{
  "compilerOptions": {
    ...
    "jsx": "react",
  },
  "angularCompilerOptions": {
    ...
  }
}

Следующим шагом мы уже можем использовать React внутри Angular приложения. 

Как мигрировать

Схема миграции простая:

  • React временно работает поверх Angular, используя его сервисы и абстракции.

  • Постепенно React-компоненты замещают Angular-код, пока приложение полностью не перейдет на нативные React-библиотеки.

  • На протяжении всей миграции приложение продолжает использовать Angular Router — это обеспечивает стабильную навигацию и минимизирует риски.

Чтобы React корректно заработал внутри Angular, необходимо решить несколько проблем:

  • синхронизация жизненного цикла;

  • управление глобальным состоянием;

  • виртуальная маршрутизация;

  • взаимодействие с сервером по HTTP.

Далее рассмотрим эти аспекты подробнее.

Синхронизация жизненного цикла

Жизненные циклы Angular и React можно свести к трем основным этапам:

  • монтирование;

  • обновление;

  • размонтирование.

Когда React-компонент встраивается в Angular, необходимо:

  • создать React-приложение при инициализации;

  • передать данные (props) и обновлять их при изменении;

  • предоставить доступ к Angular-сущностям;

  • корректно уничтожить React-дерево при ngOnDestroy.

Контекст Angular

Для использования функционала Angular из React кода можно воспользоваться Context API. Создадим такой контекст:

export interface NgContextState {
  injector: Injector;
}
export const NgContext = createContext<NgContextState>({
  injector: Injector.create({providers: []})
});

NgContext — это инструмент, который позволяет получать доступ к любым сущностям Angular через его injector.

Он решает не только основную задачу — организацию навигации между Angular и React, — но и служит мостом между двумя мирами.

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

Например, через NgContext можно вызывать уже готовые Angular-сервисы для:

  • показа уведомлений,

  • открытия диалогов,

  • выполнения бизнес-логики, которая еще не перенесена в React.

На практике не стоит использовать NgContext слишком часто — только при реальной необходимости. Чрезмерное применение создает избыточную связанность между Angular и React, от которой потом придется избавляться при полном переходе на React.

Примечание: Не стоит использовать NgContext вне тех сущностей, которые будут описаны далее в статье. 

Вспомогательная директива

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

@Directive({
  selector: "[react], app-react",
  standalone: true,
})
export class HostReact<T> implements AfterViewInit, OnDestroy {
  private readonly injector = inject(INJECTOR);
  private readonly elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
  private readonly root = signal<Root | null>(null);
  /* React компонент */
  readonly react = input.required<FC<T>>();
  /* Пропсы переданного React компонента */
  readonly props = input<T>();

  constructor() {
    /**
     * Следим за изменениями всех зависимостей React.
     * Функция effect в Angular выполняет переданный ей callback каждый раз,
     * когда один из сигналов получит обновленное значение.
     */
    effect(() => {
      const Component = this.react() as FC;
      this.root()?.render(
        <NgContext value={{ injector: this.injector }}>
          <Component {...(this.props() || {})} />
        </NgContext>,
      );
    });
  }
  /* Инициализируем root при готовности HTML элемента */
  ngAfterViewInit(): void {
    this.root.set(createRoot(this.elementRef.nativeElement));
  }
  /* Размонтируем root при уничтожении Angular компонента */
  ngOnDestroy(): void {
    this.root()?.unmount();
  }
}

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

Пример использования:

interface Props {
  user: string;
  onClick: () => void;
}

const UserCard: FC<Props> = ({ user, onClick }) => {
  return <div onClick={onClick}>{user}</div>
};

@Component({
  selector: 'app-page',
  imports: [HostReact],
  template: `
    <p>Ниже будет UserCard</p>
    <app-react
      [react]="UserCard"
      [props]="{user: user, onClick: userClick}"
    />
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export default class PageMainComponent {
  protected readonly UserCard = UserCard;
  protected readonly user = '';
  // Используем стрелочную, чтобы не потерять контекст
  protected userClick = () => {}
}

Примечание: Следует избегать передачи пропсов из Angular в React, если это возможно: чем меньше точек интеграции, тем проще будет окончательно удалить Angular. Используйте props только там, где это временно необходимо — например, когда вы постепенно заменяете Angular-компоненты на React и не можете переписать страницу целиком.

Управление глобальным состоянием

Во время миграции с Angular на React важно не потерять одно из самых ценных — состояние приложения. Оно уже, скорее всего, существует в проекте: где-то в сервисах Angular, где-то в хранилищах вроде NgRx, или как угодно еще.

В типичном приложении в таком состоянии хранятся:

  • данные пользователя;

  • настройки интерфейса;

  • справочники и сущности;

  • флаги и параметры, влияющие на работу разных компонентов.

Когда React и Angular работают вместе, нам нужен единый источник правды — общее состояние, к которому могут обращаться обе стороны. Можно выделить следующие технические требования:

  • Работает вне контекста React и Angular.

  • Предоставляет данные и уведомляет об изменениях.

  • Актуален после миграции.

Ключевая идея — вынести состояние в отдельную сущность (например, отдельный модуль store.ts) и создать небольшие «мосты» для работы с ним из Angular и React.

Когда Angular окончательно уйдёт, мост к нему можно просто удалить — React-приложение продолжит использовать тот же store без изменений.

Виртуальная маршрутизация

Во время миграции React по-прежнему будет использовать Angular Router — это логично, ведь на данном этапе маршрутизация всего приложения по-прежнему управляется Angular.

Однако, чтобы React-компоненты не зависели напрямую от Angular, стоит заранее продумать прослойку, которая спрячет детали реализации и позволит в будущем безболезненно перейти на React Router или TanStack Router.

Для этого можно создать две простые сущности для React:

  • useRouter — хук, предоставляющий API для навигации;

  • RouterLink — компонент-ссылка, использующий этот хук внутри.

На этапе миграции useRouter будет обращаться к Angular Router через NgContext, а после удаления Angular — просто сменит реализацию на новый API, сохранив тот же интерфейс.

Такое разделение дает сразу несколько плюсов:

  • React-код не знает о существовании Angular.

  • Переходы между страницами работают, как прежде.

  • Миграция навигации в будущем сводится к замене реализации в одном месте.

Простейший пример реализации хука:

export const useRouter = () => {
    // получаем роутер из Angular
    const router = useContext(NgContext).injector.get(Router);

    function navigate(href: string) {
        const url = router.parseUrl(href);
        return router.navigateByUrl(url);
    }

    function isActive(href: string, exact = false) {
        const url = router.parseUrl(href);
        return router.isActive(url, exact);
    }
    return {state, navigate, isActive};
};

Такой подход к изоляции инфраструктурных слоев — ключ к спокойной миграции. Когда Angular уйдёт, у вас не будет сотен мест, где нужно менять router.navigateByUrl(), — всё уже будет спрятано за единым интерфейсом.

Взаимодействие с сервером по HTTP

Работа с HTTP во время миграции — одна из самых сложных частей.

В уже существующем проекте, скорее всего, активно используется HttpClient из Angular, обернутый в сервисы, с перехватчиками (interceptors) и встроенной системой обработки ошибок.

Однако напрямую использовать Angular HttpClient из React нельзя — это создаст жесткую связку, которая позже сильно усложнит финальную миграцию.

Поэтому в первую очередь нужно создать единый HTTP-клиент, который:

  • работает вне контекста Angular и React;

  • может временно интегрироваться с Angular через адаптер;

  • после удаления Angular не потребует доработок.

Как и со стором, мы выносим сетевой слой за пределы Angular. На его место становится новый клиент на основе Axios — легкий и гибкий инструмент для работы с HTTP.

Идея следующая:

  • Создаём общий клиент на Axios.

  • Переносим в него нужные interceptors (авторизация, обработка ошибок и так далее).

  • Используем этот клиент напрямую из React.

  • Для Angular пишем интерсептор-прокси, который перехватывает запросы, пересылает их в Axios и возвращает результат обратно в Angular.

Пример Angular интерцептора, который проксирует запросы в Axios:

export const httpInterceptor: HttpInterceptorFn = (req) => {
  /**
   * Замена HttpClient со стандартного на Axios.
   * Нужно для интеграции http client'ов между Angular и React.
   *
   * - Получаем конфигурацию http запроса из Angular HttpClient
   * - Создаем запрос через HttpClient Axios
   * - Подменяем для Angular HttpRequest для корректной работы Angular
   */
  return from(
    AxiosClient.request({
      method: req.method,
      data: req.body,
      url: req.url,
      params: req.params,
      headers: req.headers,
    }),
  ).pipe(
    map((response) => {
      const body = response?.data;
      return new HttpResponse({
        body,
        url: req.url,
        status: response.status,
        statusText: response.statusText,
        headers: req.headers,
      });
    }),
  );
};

Таким образом, весь проект работает через один общий HTTP-клиент, а после удаления Angular просто исчезнет прослойка-прокси.

В итоге общая схема будет иметь примерно следующий вид:

При такой реализации React ничего не знает о существовании Angular, а Angular — о React. На финале миграции достаточно будет удалить все «красное» на схеме, а также избавиться от всех использований NgContext.

Схематично от и до

Изначально у нас есть только Angular-приложение

При старте миграции мы выносим стейт и HTTP-клиент из Angular, а с помощью директивы HostReact начинаем переписывать UI на React.

Перед финалом должна сложиться такая картинка: весь UI на React, а единственные Angular-компоненты, используемые на проекте — это обертки над страницами, использующие HostReact. 

В конце мы удаляем все, что связано с Angular, мигрируем сборку на vite (или другой сборщик), а также конфигурацию роутинга на react-router, tanstack/router или на любой другой. Не забываем подменить API навигации в useRouter.

Все «красное» на схеме исчезло, а значит, миграция прошла успешно.

Что в итоге

Миграция с Angular на React — довольно специфическая задача, но по мере роста проектов и изменения бизнес-целей с необходимостью смены фреймворка сталкиваются многие компании. Вместе с тем, даже несмотря на различия технологий и их структур, оптимальные способы миграции есть и разобранный в статье сценарий наглядно демонстрирует — при соблюдении простых рекомендаций, сменить фреймворк можно с минимумом рисков и издержек.

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

Комментарии (9)


  1. FluffyArt
    20.10.2025 18:18

    Сравнивать DI и context api от react, это вы, конечно, придумали...

    И с учётом того, что ангуляр идёт к упрощению, оптимизациям, динамичным рендером и улучшению DX, переход на костыльный реакт, который пытается на себе замкнуть версель, выглядит не айс, если честно


    1. al-march Автор
      20.10.2025 18:18

      Сравнение, конечно же, условно. Context api в сравнении с DI выглядит как детская игрушка, но из коробки в React у нас ничего больше нет


      1. FoxyGDFD
        20.10.2025 18:18

        из коробки в React у нас ничего больше нет

        Не могу согласиться, вы можете подключить к react любые хранилища/сервисы/api браузера и т.п. через useSyncExternalStore. Для его использования даже провайдеров не нужно никаких, возможно этот вариант покажется более удобным.


        1. al-march Автор
          20.10.2025 18:18

          Я так понимаю смутило выражение «из коробки». Оно означает, что в самом реакте для передачи контекста есть только Context api. В Angular же полноценный DI поставляется самой командой Angular


  1. frostsumonner
    20.10.2025 18:18

    - Очевидный риск: На миграцию надо много ресурсов, если в процессе их не будет хватать, то мы потратили кучу времени и остались с гибридным приложением, которое поддерживать очень сложно.
    - Очевидных плюсов я не вижу: да React популярнее Angular, а Python популярнее Go. А через 5 лет может XYZ.js будет самым популярным... Как часто надо мигрировать на популярный Framework?
    - React проше Angular потому что в React нет routing, http, DI, forms... И для всего этого нужно подключать доп. библиотеки, которые в каждом проекте свои? Или react проще, потому что в нем "Односторонний поток данных " - так не используйте two-way binding. Или react проще потому что "шаблон и логика объединены в JSX " - так оставьте template в классе компонента и ,о чудо, теперь в Angular тоже шаблон и логика объединены в одном файле.


    1. al-march Автор
      20.10.2025 18:18

      Я бы никому не рекомендовал мигрировать. Миграцию проводить долго, нудно, требует довольно тщательного планирования и квалификации команды.

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


      1. Kenya-West
        20.10.2025 18:18

        Посмею всё-таки резюмировать, ибо я прям насквозь всех этих "миграторов" вижу:

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

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


  1. kemsky
    20.10.2025 18:18

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