Let's talk.

Whether it's a project, a role, or you just want to say hi, my inbox is open.

kadhar.mfn@gmail.com
AboutExperienceProjectsWritingFree guidesToolsGames

© 2026 Kadharmoideen Fadurudeen. Made with care.

Kadhar
AboutExperienceProjectsWritingFree guides
Tools
Say hello
Back to Blog
Architecture
October 15, 2026
16 min read

Framework-Agnostic Frontend Architecture: Migrate from Angular to React Without a Rewrite

Make the UI framework a swappable detail: MVC vs. React's 'just the view', a plain-TypeScript core, Web Components, an app shell that runs Angular and React side by side, and a safe step-by-step migration plan, with a live demo.

Sooner or later, most long-lived frontends face the same question: should we move to a different framework? Maybe hiring for Angular has become harder, maybe the team wants React's ecosystem, or maybe the app simply outgrew its original choices. The usual answer, a big-bang rewrite, is also the usual way these projects fail: months of frozen features, two codebases to maintain, and a launch date that keeps slipping.

There's a better way to think about it. If most of your application doesn't know which framework it runs in, then changing frameworks stops being a rewrite and becomes a series of small, safe moves. This post covers how to build that kind of framework-agnostic frontend, why the old MVC idea is the key to it, and a step-by-step plan to migrate from Angular to React (or the other way round) without stopping the business.

Where This Fits

This builds on two earlier posts. Building Reusable Components with LitElement covers how to build Web Components, and Buffet: A Microfront-end Architecture covers splitting an app between independent teams. Here, both ideas are put to work for one specific goal: making the UI framework a replaceable detail.

MVC vs. “Just the View”

The classic Model-View-Controller pattern splits an application into three jobs. The model holds data and business rules, the view shows it, and the controller responds to user actions and updates the model. Frameworks have blurred these lines over the years, but the split is still the most useful lens for a migration.

Angular is a full framework. Out of the box it gives you components and templates (the view), services with dependency injection (a natural home for models and controller-like logic), a router, a forms system and an HTTP client. It's an opinionated, batteries-included toolkit, which is a big reason teams pick it.

React is, famously, “just the V.” It renders UI and leaves routing, data fetching, forms and state architecture to you or to other libraries. That flexibility is a strength, but it means an Angular → React move isn't like-for-like: you're replacing a whole toolkit with a view library plus several choices.

ConcernAngularReact
Rendering the viewComponents + templatesComponents + JSX
Business logicServices (via DI)Your choice: hooks, plain modules
StateServices, RxJS, signalsuseState, context, external stores
RoutingBuilt in (Angular Router)Separate library or meta-framework
FormsBuilt in (reactive & template forms)Separate library or hand-rolled
HTTPBuilt in (HttpClient)fetch or a data-fetching library

Here's the insight that makes everything else work: the expensive part of a migration is never the view. Templates can be translated almost mechanically. What hurts is business logic buried inside Angular services, tangled with dependency injection, RxJS pipelines and lifecycle hooks. If the model and controller logic lived in plain TypeScript from the start, a framework migration would only ever be a view migration. That's the whole idea of a framework-agnostic frontend.

The Framework-Agnostic Architecture

Think of the frontend as four layers. Only the top two know which framework is in use, and both are deliberately thin.

  1. 4App shellOwns routing between old and new pages, authentication, and the page frame. Lets Angular and React live side by side during the move.
  2. 3Framework adaptersTiny glue: a React hook or an Angular service that connects the core to each framework. Usually a few dozen lines each.
  3. 2Shared UI (Web Components + design tokens)Buttons, inputs, cards, dialogs built once as standard custom elements and styled by CSS variables, usable in both frameworks.
  4. 1Core (plain TypeScript)Models, business rules, validation, API clients and state stores. The most valuable code you own, and it never imports a framework.

1. A core in plain TypeScript

The core is everything that would still be true if you deleted every component: your data models, pricing and validation rules, API clients, and state. A store doesn't need a library to be useful. It needs a way to read the current value and a way to be told when it changes:

core/task-store.ts
// core/task-store.ts: no React, no Angular, just TypeScript
type Listener = () => void

export function createTaskStore(initial: Task[]) {
  let tasks = initial
  const listeners = new Set<Listener>()

  return {
    getSnapshot: () => tasks,
    subscribe(listener: Listener) {
      listeners.add(listener)
      return () => listeners.delete(listener)
    },
    toggle(id: number) {
      tasks = tasks.map(t => (t.id === id ? { ...t, done: !t.done } : t))
      listeners.forEach(l => l())
    },
  }
}

export const taskStore = createTaskStore([])

This is the same pattern successful open-source projects use. TanStack Query, Table and Form each ship one framework-free core with small adapters for React, Angular, Vue and Solid. You can apply the same design to your own product code. If you prefer a library, choose one that is framework-agnostic itself (plain observable stores, state machines, or a signals library), not one that lives inside a framework.

2. Thin adapters for each framework

Each framework gets a small wrapper that turns the store into something idiomatic. In React that's one hook built on useSyncExternalStore, which exists precisely to connect React to stores it doesn't own:

react/use-tasks.ts
// react/use-tasks.ts: the whole React adapter
import { useSyncExternalStore } from "react"
import { taskStore } from "../core/task-store"

export function useTasks() {
  return useSyncExternalStore(taskStore.subscribe, taskStore.getSnapshot)
}

In Angular it's an injectable service that mirrors the store into a signal, so templates stay reactive the modern Angular way:

angular/tasks.service.ts
// angular/tasks.service.ts: the whole Angular adapter
import { Injectable, signal, OnDestroy } from "@angular/core"
import { taskStore } from "../core/task-store"

@Injectable({ providedIn: "root" })
export class TasksService implements OnDestroy {
  readonly tasks = signal(taskStore.getSnapshot())
  private unsubscribe = taskStore.subscribe(() => this.tasks.set(taskStore.getSnapshot()))

  toggle(id: number) {
    taskStore.toggle(id)
  }

  ngOnDestroy() {
    this.unsubscribe()
  }
}

Notice what's missing: no business rules in either adapter. When the Angular side is eventually deleted, nothing of value goes with it. Signals are also worth watching here. Angular, Vue, Solid and Preact all use them, and there's an early-stage proposal to add signals to JavaScript itself, which would make shared reactive state even more natural across frameworks.

3. Shared UI with Web Components

Your design system is the other big asset worth protecting. Web Components (standard custom elements) run anywhere HTML runs. Angular has supported them for years via CUSTOM_ELEMENTS_SCHEMA, and React 19 closed the long-standing gap by fully supporting custom elements, including setting properties and listening to custom events. In practice that means one <ds-button> can serve both apps.

Start even simpler with design tokens as CSS variables (colours, spacing, radii, type). Custom properties pass through Shadow DOM, so the same tokens style Angular components, React components and Web Components alike. You'll see that working in the demo below.

4. An app shell for coexistence

During the migration, both frameworks have to run in production at the same time. There are three common ways to arrange that, from lightest to heaviest:

  • React islands inside Angular. An Angular component mounts a React root into its element. The simplest way to ship your first React page.
  • Components as custom elements. Angular Elements can expose Angular components as Web Components, and React components can be wrapped the same way, so either side can host the other.
  • A shell with route-level ownership. A lightweight shell (or a micro-frontend setup like Module Federation or single-spa) decides per route whether Angular or React renders the page. Best for large apps and multiple teams.

Here's how small the island approach can be:

angular/react-island.component.ts
// angular/react-island.component.ts: host a React page inside Angular
import { Component, ElementRef, OnDestroy, AfterViewInit, Input } from "@angular/core"
import { createRoot, type Root } from "react-dom/client"
import { createElement } from "react"
import { SettingsPage } from "../react/settings-page"

@Component({ selector: "app-react-settings", standalone: true, template: "" })
export class ReactSettingsIsland implements AfterViewInit, OnDestroy {
  @Input() userId!: string
  private root?: Root

  constructor(private host: ElementRef<HTMLElement>) {}

  ngAfterViewInit() {
    this.root = createRoot(this.host.nativeElement)
    this.root.render(createElement(SettingsPage, { userId: this.userId }))
  }

  ngOnDestroy() {
    this.root?.unmount()
  }
}

Try It: One Store, Two Worlds

Below, one plain-TypeScript store drives two different UIs at once. The left panel is React. The right panel is a framework-free Web Component, the kind you could drop straight into an Angular template. Tick a task or add one on either side and watch the other update instantly.

Shared store: 1 of 3 done · last change from initial data

React view· useSyncExternalStore

Web Component view· no framework

Loading the Web Component…

Neither view owns the data. Both subscribe to the same plain-TypeScript store. The right-hand panel is a standard custom element: drop <agnostic-task-board> into an Angular template and it behaves exactly the same. Its colours come from the site's CSS variables, which pass through Shadow DOM, so one set of design tokens styles both worlds.

A Safe, Step-by-Step Migration Plan

This is the strangler fig pattern applied to a frontend: grow the new system around the old one, one piece at a time, until the old one can be removed. Every phase ships to production and leaves the app in a working state.

  1. 0

    Assess and set a safety net

    Inventory routes and modules, map where business logic lives, and capture baseline metrics (load time, error rate, conversion). Write end-to-end tests that drive the browser, not the framework. They will keep working after the migration and prove nothing broke.

  2. 1

    Extract the core

    Move business rules, validation, API calls and state out of Angular services into plain TypeScript modules. The Angular services become thin adapters. Users see no change, which makes this phase low-risk and easy to ship in small pull requests.

  3. 2

    Share the design system

    Start with design tokens as CSS variables, then move the most-used components (button, input, modal) to Web Components. Both frameworks now render the same look, so mixed pages don't feel stitched together.

  4. 3

    Set up coexistence

    Introduce the app shell or a React-island wrapper inside Angular. Agree the contracts: how routes are owned, how auth tokens are shared, and how the two sides send events. Ship one trivial React page end-to-end to prove the plumbing.

  5. 4

    Migrate route by route

    Move one page at a time, starting with high-value, low-risk ones. Release each behind a feature flag, roll it out gradually, compare metrics against the baseline, and keep the Angular version until the new one is proven. Rollback is a flag flip.

  6. 5

    Decommission

    When the last route moves, remove Angular, its build tooling and the adapters you no longer need. Check the bundle size drops as expected and celebrate. This is the step teams forget to schedule.

A route is ready to move when…

  • ✓ Its business logic already lives in the core, not in Angular services
  • ✓ The shared components it needs exist in the design system
  • ✓ End-to-end tests cover its main user journeys
  • ✓ It can be switched back to Angular with a feature flag
  • ✓ You know which metrics would tell you something went wrong

Where AI Coding Agents Help (and Where They Don't)

In 2026, AI coding agents are genuinely good at the mechanical parts of a migration: translating an Angular template into JSX, turning a service into a hook, generating adapter boilerplate, and writing the end-to-end tests that prove behaviour didn't change. Used well, they can shrink the “migrate route by route” phase dramatically.

But they work best on exactly the architecture described above. When logic is tangled into framework code, an agent has to reinterpret it, and that's where subtle bugs creep in. When the core is already separated, the agent only translates the view, which is a much smaller and safer job. The architectural decisions (layer boundaries, coexistence strategy, rollout order) are still a human job, and every generated change still deserves a proper review.

Honest Trade-offs

Two frameworks, two costs

While both run, users download both. Keep the coexistence period deliberate and measured, lazy-load the framework a route doesn't use, and track bundle size as a migration metric.

Server rendering is harder

Web Components and SSR still need care. Declarative Shadow DOM helps, but test SEO-critical and above-the-fold pages early rather than assuming they'll render server-side.

Forms and accessibility

Inputs inside Shadow DOM don't automatically join native forms or label relationships. Use form-associated custom elements (ElementInternals) and test with a screen reader.

Don't over-abstract

Not everything should be agnostic. Page layouts and one-off screens can be written directly in the target framework. Keep the core for logic that is truly shared and long-lived.

When You Shouldn't Migrate at All

Modern Angular, with standalone components, signals and zoneless change detection, is a very different framework from the one many teams adopted years ago. Before committing to a move, check that the reason is real: hiring, ecosystem, or a product direction the current stack can't support, not just fashion. And for a small app with a handful of screens, a clean rewrite may honestly be cheaper than building coexistence. Either way, the core-plus-adapters structure pays off: it makes your code easier to test and reason about even if you never switch frameworks.

Frequently Asked Questions

What is a framework-agnostic frontend architecture?

It's a way of structuring a frontend so that business logic, state, data access and shared UI components don't depend on a specific framework like React or Angular. The framework is reduced to a thin view layer with small adapters, so it can be replaced or mixed with another framework without rewriting the rest of the app.

Can Angular and React run in the same application?

Yes. The common approaches are mounting React components inside Angular components (or the reverse), wrapping components as Web Components with Angular Elements or a React wrapper, and using an app shell or micro-frontend setup that routes some pages to Angular and others to React. Running both is a temporary cost that makes incremental migration possible.

Is React only the 'V' in MVC?

Roughly, yes. React is a library for rendering UI, so it covers the view and leaves routing, data fetching, forms and state architecture to other libraries. Angular is a full framework that ships the view plus services, dependency injection, routing, forms and an HTTP client, which is closer to a complete MVC-style toolkit. That difference is why moving logic out of Angular-specific services is the key first step of a migration.

Do Web Components work in React and Angular?

Yes. Angular has supported custom elements for a long time through CUSTOM_ELEMENTS_SCHEMA. React 19 added full support for custom elements, including passing properties and listening to custom events, which removed the main blocker for using one Web Component design system across both frameworks.

Should we rewrite our Angular app in React or migrate incrementally?

For most production apps, migrate incrementally. Big-bang rewrites freeze feature work, duplicate effort and tend to slip. An incremental, route-by-route migration keeps shipping value, lets you roll back individual pages and spreads risk. A full rewrite only makes sense for small apps or apps whose design is changing completely anyway.

How long does an Angular to React migration take?

It depends far more on how tangled the business logic is with Angular than on the number of screens. Teams that first extract logic into plain TypeScript usually find the view migration becomes steady, predictable work per route. Plan for coexistence lasting months, not weeks, and measure progress as routes migrated and Angular code removed.

Conclusion

Frameworks come and go. Your business rules, your data, and your design system are what last. Keep them in plain TypeScript and Web Components, connect them to whatever framework you use today through thin adapters, and a future migration becomes a steady sequence of small, reversible steps instead of a risky rewrite. Treat the framework as the view it really is, and it stops being the most expensive decision in your frontend.

PreviousCombo Maker: Building a Budget-Aware Meal Recommender

Continue Reading

Explore more articles on software engineering and technology

Architecture

Buffet: A Delightful Microfront-end Architecture

Introducing Buffet, a revolutionary microfront-end architecture designed to dramatically increase app delivery velocity. Learn how this modular approach enables teams to work independently while maintaining a cohesive user experience.

15 min read
Read
AI Agents

Combo Maker: Building a Budget-Aware Meal Recommender

Give it a budget and a diet preference, and Combo Maker assembles the best meal combo it can afford from a sample menu — a live demo, how the allocator works, and an honest look at what a real AI-backed version would need.

9 min read
Read
AI Tools

Top 10 Platforms to Create AI-Powered Courses and Get Certified

From Coursera and DeepLearning.AI to newer entrants like Nokku.ai — a practical rundown of where to take AI-generated courses, actually finish them, and walk away with a certificate that means something.

10 min read
Read