---
# === IDENTITY ===
id: software/migrations/angular-to-react/2026
canonical_question: "How do I migrate from Angular to React?"
aliases:
  - "Angular to React migration guide"
  - "convert Angular app to React"
  - "replace Angular with React"
  - "Angular vs React migration"
  - "switch from Angular to React"
  - "Angular to React rewrite strategy"
  - "Angular React strangler fig migration"
entity_type: software_reference
domain: software > migrations > angular_to_react
region: global
jurisdiction: global
temporal_scope: 2020-2026

# === VERIFICATION ===
last_verified: 2026-05-29
confidence: 0.91
version: 2.1
first_published: 2026-02-16

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: evolving
  last_breaking_change: "Angular 20 (May 2025) — signals (effect, linkedSignal, toSignal) stable, zoneless change detection stable as of 20.2; React 19.2 (Oct 2025) + React Compiler v1.0 (Oct 2025) — automatic memoization, forwardRef deprecated, use()/Actions/useActionState/useOptimistic"
  next_review: 2026-11-25
  change_sensitivity: medium

# === CONSTRAINTS ===
constraints:
  - "Angular version must be 14+ for standalone component support; Angular 17+ recommended for new control flow syntax (@if, @for) that maps more naturally to JSX"
  - "RxJS heavy codebases (>200 subscribe() calls) require a dedicated refactoring phase before React migration — do not bridge RxJS into React components"
  - "TypeScript strict mode settings must be preserved across the migration — copy strict, noImplicitAny, strictNullChecks from Angular tsconfig to React tsconfig"
  - "zone.js must be isolated or disabled (NgZone: 'noop') during coexistence — it patches global async APIs and causes unexpected React behavior"
  - "Never run a big-bang rewrite for apps >10K LOC — use Strangler Fig incremental migration to avoid 6+ month feature freezes"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "User is migrating from AngularJS (1.x), not Angular (2+)"
    use_instead: "software/migrations/angularjs-to-react/2026"
  - condition: "User wants to upgrade Angular version (e.g., 14 to 20) without switching to React"
    use_instead: "software/migrations/angular-upgrade/2026"
  - condition: "User is migrating from AngularJS (1.x) to modern Angular (2+)"
    use_instead: "software/migrations/angularjs-to-angular/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: angular_version
    question: "Which Angular version is the app currently running? (14, 15, 16, 17, 18, 19, 20, 21)"
    type: choice
    options: ["14", "15", "16", "17", "18", "19", "20", "21"]
  - key: app_size
    question: "Roughly how large is the Angular codebase?"
    type: choice
    options: ["Small (<10K LOC)", "Medium (10K-50K LOC)", "Large (50K-200K LOC)", "Enterprise (>200K LOC)"]
  - key: rxjs_usage
    question: "How heavily does the app use RxJS (Observables, BehaviorSubjects, complex pipe chains)?"
    type: choice
    options: ["Light (mostly HTTP calls)", "Moderate (some BehaviorSubjects, simple pipes)", "Heavy (complex orchestration with switchMap, combineLatest, custom operators)"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/migrations/angular-to-react/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-05-29)"

# === RELATED UNITS ===
related_kos:
  related_to:
    - id: "software/migrations/angularjs-to-angular/2026"
      label: "AngularJS to Angular Migration"
    - id: "software/migrations/jquery-to-react/2026"
      label: "jQuery to React Migration"
    - id: "software/migrations/javascript-to-typescript/2026"
      label: "JavaScript to TypeScript Migration"
  alternative_to:
    - id: "software/migrations/react-classes-to-hooks/2026"
      label: "React Class Components to Hooks (if already on React)"
  often_confused_with:
    - id: "software/migrations/angularjs-to-react/2026"
      label: "AngularJS (1.x) to React Migration — different framework, different patterns"

# === SOURCES (10 authoritative sources) ===
# Types: official_docs, technical_blog, rfc_spec, academic_paper, community_resource, industry_report
# Reliability: high, moderate_high, moderate, moderate_low, low, authoritative
sources:
  - id: src1
    title: "Quick Start — React"
    author: React (Meta)
    url: https://react.dev/learn
    type: official_docs
    published: 2024-01-01
    reliability: authoritative
  - id: src2
    title: "Introduction to Services and Dependency Injection"
    author: Angular (Google)
    url: https://angular.dev/guide/di
    type: official_docs
    published: 2024-06-01
    reliability: authoritative
  - id: src3
    title: "Strategies for Migrating from Angular to ReactJS"
    author: Makers' Den
    url: https://makersden.io/blog/angular-to-react-migration-strategies
    type: technical_blog
    published: 2024-09-15
    reliability: high
  - id: src4
    title: "The Guide to Converting Angular Apps to React"
    author: LogRocket
    url: https://blog.logrocket.com/guide-converting-angular-apps-react/
    type: technical_blog
    published: 2024-03-20
    reliability: high
  - id: src5
    title: "Angular to React Migration: A Comprehensive Guide"
    author: Prioxis
    url: https://www.prioxis.com/blog/angular-to-react
    type: technical_blog
    published: 2025-06-01
    reliability: high
  - id: src6
    title: "Strategy and Tips for Migrating to React"
    author: Brainhub
    url: https://brainhub.eu/library/migrating-to-react
    type: technical_blog
    published: 2025-01-10
    reliability: moderate_high
  - id: src7
    title: "Building Angular and React Microfrontends with Single-SPA and Module Federation"
    author: Roshan Gavandi
    url: https://roshancloudarchitect.me/building-angular-and-react-microfrontends-with-single-spa-and-webpack-module-federation-a-9f65fb6e179b
    type: community_resource
    published: 2024-11-05
    reliability: moderate_high
  - id: src8
    title: "React v19 — New Features and Migration Guide"
    author: React (Meta)
    url: https://react.dev/blog/2024/12/05/react-19
    type: official_docs
    published: 2024-12-05
    reliability: authoritative
  - id: src9
    title: "Announcing Angular v20"
    author: Angular (Google)
    url: https://blog.angular.dev/announcing-angular-v20-b5c9c06cf301
    type: official_docs
    published: 2025-05-28
    reliability: authoritative
  - id: src10
    title: "React Versions"
    author: React (Meta)
    url: https://react.dev/versions
    type: official_docs
    published: 2026-05-06
    reliability: authoritative
---

# How to Migrate from Angular to React

## TL;DR

- **Bottom line**: Use an incremental Strangler Fig migration -- run Angular and React side by side via Module Federation or single-spa, migrate route by route, and decommission Angular modules only after their React replacements are production-tested.
- **Key tool/command**: `npx create-single-spa` or Webpack 5 Module Federation to orchestrate Angular/React coexistence during migration.
- **Watch out for**: Porting Angular's dependency injection (DI) system 1:1 into React -- React has no DI container; services become custom hooks, context providers, or plain modules.
- **Works with**: Angular 14-20 (20 recommended; 21 LTS, 22 signal-first), React 18-19.2 + React Compiler v1.0, TypeScript 5.x+, single-spa 6+, Webpack 5 / Rspack Module Federation.

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- **Angular 14+ required**: Standalone component support is needed for incremental migration. Angular 17+ with new control flow (`@if`, `@for`) maps more naturally to JSX and simplifies conversion. Angular 20+ (signals stable, zoneless stable as of 20.2) is ideal — its signal-first model is conceptually closest to React.
- **RxJS must be refactored before migration**: Codebases with >200 `subscribe()` calls require a dedicated RxJS simplification phase. Do not bridge RxJS Observables into React components -- they break Suspense, Concurrent Mode, and Server Components.
- **TypeScript strictness must be preserved**: Copy `strict`, `noImplicitAny`, `strictNullChecks` settings from Angular's tsconfig to React's tsconfig. Loosening strictness during migration introduces regressions.
- **zone.js must be isolated during coexistence**: Angular's zone.js patches global async APIs (setTimeout, Promise, fetch). Configure Angular with `NgZone: 'noop'` or isolate its zone to prevent unexpected React behavior.
- **No big-bang rewrites for apps >10K LOC**: Use Strangler Fig pattern. Feature freezes longer than 2-3 months cause scope creep, missed deadlines, and regressions discovered only at go-live.

## Quick Reference

| Angular Pattern | React Equivalent | Example |
|---|---|---|
| `@Component({ template })` | Function component + JSX | `function App() { return <div>Hello</div>; }` |
| `@Injectable() class DataService` | Custom hook or plain module | `function useData() { ... }` or `export const dataService = { ... }` |
| `constructor(private svc: DataService)` (DI) | `useContext()` or direct import | `const data = useContext(DataContext)` |
| `this.http.get<T>(url)` (HttpClient) | `fetch` / React Query / SWR | `const { data } = useQuery({ queryKey: ['key'], queryFn: () => fetch(url) })` |
| `observable$.subscribe(val => ...)` (RxJS) | `useState` + `useEffect` or React Query | `const [val, setVal] = useState(null); useEffect(() => { ... }, [])` |
| `signal()` (Angular 16+) | `useState` / `useSignal` (Preact Signals) | `const [count, setCount] = useState(0)` |
| `@Input() name: string` | Props | `function Child({ name }: { name: string }) { ... }` |
| `@Output() clicked = new EventEmitter()` | Callback props | `function Child({ onClick }: { onClick: () => void }) { ... }` |
| `*ngIf="condition"` / `@if (condition)` | Conditional rendering | `{condition && <Component />}` |
| `*ngFor="let item of items"` / `@for (item of items)` | `.map()` in JSX | `{items.map(item => <Item key={item.id} {...item} />)}` |
| `[(ngModel)]="value"` (two-way binding) | Controlled input | `<input value={val} onChange={e => setVal(e.target.value)} />` |
| `RouterModule.forRoot(routes)` | React Router `<Routes>` | `<Routes><Route path="/" element={<Home />} /></Routes>` |
| `canActivate: [AuthGuard]` (route guard) | Wrapper component or loader | `<ProtectedRoute><Dashboard /></ProtectedRoute>` |
| `@Pipe({ name: 'currency' })` | Plain function or `useMemo` | `const formatted = useMemo(() => formatCurrency(val), [val])` |
| `@NgModule({ imports, declarations })` | No equivalent -- just ES imports | React has no module system; use ES modules and `React.lazy()` |
| `HttpInterceptor` | Fetch wrapper or Axios interceptor | `const api = axios.create(); api.interceptors.request.use(...)` |
| Reactive Forms (`FormGroup`, `FormControl`) | React Hook Form or `useActionState` (React 19) | `const { register, handleSubmit } = useForm<FormData>()` |
| `| async` pipe | `use()` hook (React 19) or `useQuery` | `const comments = use(commentsPromise)` |
| `Meta` / `Title` services | Direct metadata in JSX (React 19) | `<title>{post.title}</title>` in component return |

## Decision Tree

```
START
├── Is the Angular app < 10K LOC with few third-party Angular libraries?
│   ├── YES → Full rewrite in React (faster than incremental for small apps)
│   └── NO ↓
├── Does the team need to ship features during migration?
│   ├── YES → Strangler Fig: use Module Federation or single-spa for coexistence
│   └── NO ↓
├── Is the Angular app on version 17+ with standalone components?
│   ├── YES → Easier migration -- standalone components map 1:1 to React components. Migrate component-by-component.
│   └── NO ↓
├── Is the app heavily module-based with lazy-loaded Angular modules?
│   ├── YES → Migrate route-by-route: replace each Angular lazy module with a React micro-frontend
│   └── NO ↓
├── Does the app rely heavily on RxJS for complex async orchestration?
│   ├── YES → First refactor RxJS → simpler async patterns (promises, Angular signals), then migrate to React hooks
│   └── NO ↓
├── Is there heavy use of Angular Material or CDK?
│   ├── YES → Choose a React UI library (MUI, Radix, shadcn/ui) first, map components, then migrate
│   └── NO ↓
└── DEFAULT → Route-by-route incremental migration with shared state via a framework-agnostic store (Zustand, custom event bus)
```

## Decision Logic

Agent-facing if/then rules for recommending a concrete migration path. Each rule is independent; evaluate top-down and stop at the first match.

### If the Angular app is < 10K LOC with few third-party Angular libraries
--> Full rewrite in React 19.2 (Vite + React Compiler v1.0) — for small apps a clean rewrite finishes faster than standing up a coexistence shell. [src3, src5]

### If the team must keep shipping features during the migration
--> Use the Strangler Fig pattern with Module Federation (Webpack 5 / Rspack) or single-spa 6+ — never freeze feature work for a big-bang rewrite of an app over 10K LOC. [src3, src7]

### If the Angular app is on v14-16 (pre-signals, NgModule-heavy)
--> Upgrade to Angular 20 first (signals stable, zoneless stable in 20.2), refactor RxJS to signals, then migrate — signals map almost 1:1 to React `useState`/`useMemo`/`useEffect`, making the final port mechanical. [src2, src9]

### If the codebase has > 200 `subscribe()` calls or complex RxJS orchestration
--> Run a dedicated RxJS-to-signals (or React Query) refactor phase before touching React; do not bridge `Observable`s into React components — they break Suspense and Server Components. [src4, src5]

### If the app leans heavily on Angular Material or CDK
--> Pick the React UI library first (MUI, Radix, shadcn/ui), build a shared component map, then migrate route-by-route so styling stays consistent across the coexistence period. [src3, src6]

### If you are targeting React 19+ for form-heavy or async-heavy screens
--> Replace Angular reactive forms and the `async` pipe with React 19 `useActionState`, `useOptimistic`, and the `use()` hook rather than older `forwardRef`/manual-subscription patterns (`forwardRef` is deprecated in React 19). [src8, src10]

### If the app is a large enterprise monolith (> 50K LOC) with multiple team domains
--> Lazy-load independent React micro-frontends via Module Federation while Angular serves the shell, so teams ship migrations without coordinating a single bundle; budget 6-18 months. [src7]

## Step-by-Step Guide

### 1. Audit the Angular codebase

Map all Angular modules, services, routes, and third-party dependencies. Categorize each module by complexity (simple, moderate, complex) and business criticality. This determines migration order and timeline. [src3, src5]

```bash
# Count Angular components, services, pipes, directives, and modules
find src -name "*.component.ts" | wc -l
find src -name "*.service.ts" | wc -l
find src -name "*.pipe.ts" | wc -l
find src -name "*.directive.ts" | wc -l
find src -name "*.module.ts" | wc -l

# Count RxJS usage (higher count = more migration effort)
grep -rn "subscribe\|Observable\|BehaviorSubject\|switchMap\|mergeMap" --include="*.ts" src/ | wc -l

# Count Angular signals usage (Angular 16+, maps easily to React useState)
grep -rn "signal(\|computed(\|effect(" --include="*.ts" src/ | wc -l

# List all third-party Angular-specific packages
cat package.json | grep -E "@angular|@ngrx|ngx-" | wc -l
```

**Verify**: You have a spreadsheet/document listing every Angular module with: component count, service count, RxJS complexity score, signals usage count, and third-party dependency list.

### 2. Set up the coexistence shell

Install Module Federation (recommended) or single-spa to run Angular and React side by side. The Angular app becomes the "host" and React apps mount as remotes. [src5, src7]

```bash
# Option A: Module Federation (Webpack 5 — recommended for new migrations)
npm install @angular-architects/module-federation
ng add @angular-architects/module-federation --project main --port 4200

# Option B: single-spa (proven for complex multi-framework setups)
npx create-single-spa --framework react --moduleType app-parcel
npm install single-spa single-spa-react

# Verify both frameworks load
npm run start  # Angular host on :4200
npm run start:react  # React remote on :3000
```

**Verify**: Both Angular and React apps load in the same browser window. Navigate between Angular routes and React routes without full page reloads.

### 3. Create a shared state layer

Angular services and React hooks cannot directly share state. Create a framework-agnostic store that both can read/write. [src3, src6]

```typescript
// shared/store.ts — framework-agnostic reactive store
type Listener = () => void;

export class SharedStore<T extends Record<string, unknown>> {
  private state: T;
  private listeners = new Set<Listener>();

  constructor(initialState: T) {
    this.state = initialState;
  }

  getState(): T {
    return this.state;
  }

  setState(partial: Partial<T>): void {
    this.state = { ...this.state, ...partial };
    this.listeners.forEach(fn => fn());
  }

  subscribe(listener: Listener): () => void {
    this.listeners.add(listener);
    return () => this.listeners.delete(listener);
  }
}

// Create a global instance
export const appStore = new SharedStore({
  user: null as { id: string; name: string } | null,
  theme: 'light' as 'light' | 'dark',
});
```

**Verify**: From the Angular console: `appStore.setState({ theme: 'dark' })` -- React components reading the store re-render instantly.

### 4. Migrate services to hooks and modules

Convert Angular `@Injectable` services into React custom hooks (for stateful/lifecycle logic) or plain TypeScript modules (for pure functions). [src1, src2]

```typescript
// BEFORE: Angular service
@Injectable({ providedIn: 'root' })
export class UserService {
  constructor(private http: HttpClient) {}

  getUsers(): Observable<User[]> {
    return this.http.get<User[]>('/api/users');
  }

  getUserById(id: string): Observable<User> {
    return this.http.get<User>(`/api/users/${id}`);
  }
}

// AFTER: React custom hook (using React Query for server state)
import { useQuery } from '@tanstack/react-query';

interface User {
  id: string;
  name: string;
  email: string;
}

export function useUsers() {
  return useQuery<User[]>({
    queryKey: ['users'],
    queryFn: async () => {
      const res = await fetch('/api/users');
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      return res.json();
    },
  });
}

export function useUser(id: string) {
  return useQuery<User>({
    queryKey: ['users', id],
    queryFn: async () => {
      const res = await fetch(`/api/users/${id}`);
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      return res.json();
    },
    enabled: !!id,
  });
}
```

**Verify**: `useUsers()` in a React component returns the same data as `UserService.getUsers()` in the Angular component. Compare network requests side by side.

### 5. Migrate routes incrementally

Replace one Angular lazy-loaded route at a time with its React equivalent. The Module Federation host (or single-spa router) decides which framework renders each URL. [src3, src7]

```typescript
// single-spa root config: register Angular and React apps by route
import { registerApplication, start } from 'single-spa';

// Angular handles /dashboard and /settings (not yet migrated)
registerApplication({
  name: 'angular-app',
  app: () => import('./angular-main'),
  activeWhen: ['/dashboard', '/settings'],
});

// React handles /users (migrated) and /reports (migrated)
registerApplication({
  name: 'react-app',
  app: () => import('./react-main'),
  activeWhen: ['/users', '/reports'],
});

start();
```

**Verify**: Navigate to `/users` -- React renders. Navigate to `/dashboard` -- Angular renders. Back button works correctly. Shared store stays in sync.

### 6. Convert template syntax and patterns

Translate Angular template directives to React JSX patterns. This is the bulk of the migration work. [src1, src4]

```typescript
// BEFORE: Angular component with template directives
@Component({
  selector: 'app-user-list',
  template: `
    <div *ngIf="loading">Loading...</div>
    <ul *ngIf="!loading">
      <li *ngFor="let user of users; trackBy: trackById"
          [class.active]="user.id === selectedId"
          (click)="selectUser(user)">
        {{ user.name | uppercase }}
      </li>
    </ul>
  `
})
export class UserListComponent implements OnInit {
  users: User[] = [];
  loading = true;
  selectedId: string | null = null;

  constructor(private userService: UserService) {}

  ngOnInit(): void {
    this.userService.getUsers().subscribe(users => {
      this.users = users;
      this.loading = false;
    });
  }

  selectUser(user: User): void {
    this.selectedId = user.id;
  }

  trackById(index: number, user: User): string {
    return user.id;
  }
}

// AFTER: React equivalent
import { useState } from 'react';
import { useUsers } from '../hooks/useUsers';

function UserList() {
  const { data: users, isLoading } = useUsers();
  const [selectedId, setSelectedId] = useState<string | null>(null);

  if (isLoading) return <div>Loading...</div>;

  return (
    <ul>
      {users?.map(user => (
        <li
          key={user.id}
          className={user.id === selectedId ? 'active' : ''}
          onClick={() => setSelectedId(user.id)}
        >
          {user.name.toUpperCase()}
        </li>
      ))}
    </ul>
  );
}

export default UserList;
```

**Verify**: Side-by-side comparison -- both render identical HTML. Click behavior and styling match. Check accessibility (keyboard navigation, ARIA attributes).

### 7. Decommission Angular and clean up

After all routes are migrated and production-tested, remove the Angular framework, coexistence shell, and shared store bridge. [src3, src6]

```bash
# Remove Angular packages
npm uninstall @angular/core @angular/common @angular/router @angular/forms \
  @angular/platform-browser @angular/platform-browser-dynamic \
  @angular/compiler @angular/compiler-cli @angular-devkit/build-angular \
  @ngrx/store @ngrx/effects rxjs zone.js single-spa single-spa-angular

# Remove Angular config files
rm -f angular.json tsconfig.app.json tsconfig.spec.json

# Search for any remaining Angular references
grep -rn "@angular\|@Injectable\|@Component\|@NgModule\|subscribe(" --include="*.ts" src/ | head -20

# Replace shared store with React-native state (Zustand, Context, etc.)
# Verify no window.appStore references remain
grep -rn "appStore\|SharedStore" --include="*.ts" --include="*.tsx" src/ | head -20
```

**Verify**: `npm ls @angular/core` returns "empty". App loads and all routes work without Angular. Bundle size decreased. CI pipeline passes all tests.

## Code Examples

### TypeScript/React: Converting an Angular service with RxJS to a React hook

> Full script: [typescript-react-converting-an-angular-service-wit.ts](scripts/typescript-react-converting-an-angular-service-wit.ts) (66 lines)

```typescript
// Input:  Angular service using RxJS BehaviorSubject for real-time state
// Output: React custom hook with equivalent behavior using useSyncExternalStore
// --- Angular original ---
// @Injectable({ providedIn: 'root' })
// export class CartService {
# ... (see full script)
```

### TypeScript/React: Converting Angular Reactive Forms to React Hook Form

> Full script: [typescript-react-converting-angular-reactive-forms.ts](scripts/typescript-react-converting-angular-reactive-forms.ts) (87 lines)

```typescript
// Input:  Angular reactive form with validators and error messages
// Output: React Hook Form equivalent with identical validation
// --- Angular original ---
// this.form = this.fb.group({
//   email: ['', [Validators.required, Validators.email]],
# ... (see full script)
```

### TypeScript: Framework-agnostic bridge for Angular-React coexistence

> Full script: [typescript-framework-agnostic-bridge-for-angular-r.ts](scripts/typescript-framework-agnostic-bridge-for-angular-r.ts) (51 lines)

```typescript
// Input:  Angular app that needs to share auth state with React micro-frontends
// Output: CustomEvent-based bridge that works across any framework boundary
// shared/event-bridge.ts — publish/subscribe via CustomEvent (DOM-native, zero deps)
interface AuthPayload {
  userId: string;
# ... (see full script)
```

### TypeScript/React: Converting Angular route guards to React protected routes

```typescript
// Input:  Angular canActivate guard
// Output: React wrapper component with React Router v6

// BEFORE: Angular guard
// @Injectable({ providedIn: 'root' })
// export class AuthGuard implements CanActivate {
//   constructor(private authService: AuthService, private router: Router) {}
//   canActivate(): boolean {
//     if (this.authService.isAuthenticated()) return true;
//     this.router.navigate(['/login']);
//     return false;
//   }
// }

// AFTER: React protected route component
import { Navigate, Outlet } from 'react-router-dom';
import { useAuth } from '../hooks/useAuth';

function ProtectedRoute() {
  const { isAuthenticated, isLoading } = useAuth();

  if (isLoading) return <div>Loading...</div>;
  if (!isAuthenticated) return <Navigate to="/login" replace />;
  return <Outlet />;
}

// Usage in router:
// <Route element={<ProtectedRoute />}>
//   <Route path="/dashboard" element={<Dashboard />} />
//   <Route path="/settings" element={<Settings />} />
// </Route>
```

## Anti-Patterns

### Wrong: Porting Angular DI system into React with a custom DI container

```typescript
// ❌ BAD — Rebuilding Angular's DI in React
class DIContainer {
  private registry = new Map<string, any>();
  register(token: string, instance: any) { this.registry.set(token, instance); }
  resolve<T>(token: string): T { return this.registry.get(token); }
}

const container = new DIContainer();
container.register('UserService', new UserService());

// In React component:
function UserList() {
  const userService = container.resolve<UserService>('UserService');
  // ...
}
```

### Correct: Use React's built-in patterns -- hooks for state, imports for logic

```typescript
// ✅ GOOD — React-idiomatic approach: custom hooks and plain imports
// For stateful logic: custom hook
export function useUsers() {
  return useQuery({ queryKey: ['users'], queryFn: fetchUsers });
}

// For stateless logic: plain module
export function formatUserName(user: User): string {
  return `${user.firstName} ${user.lastName}`;
}

// In component:
function UserList() {
  const { data: users } = useUsers();
  return <ul>{users?.map(u => <li key={u.id}>{formatUserName(u)}</li>)}</ul>;
}
```

### Wrong: Converting RxJS observables to React by subscribing inside useEffect

```typescript
// ❌ BAD — Manual RxJS subscription in React (memory leaks, no Suspense support)
function UserProfile({ userId }: { userId: string }) {
  const [user, setUser] = useState<User | null>(null);

  useEffect(() => {
    const sub = userService.getUser(userId).pipe(
      switchMap(user => userService.getActivity(user.id)),
      catchError(err => of(null))
    ).subscribe(data => setUser(data));
    return () => sub.unsubscribe();
  }, [userId]);

  return <div>{user?.name}</div>;
}
```

### Correct: Replace RxJS with React Query or native async patterns

```typescript
// ✅ GOOD — React Query handles caching, refetching, error states automatically
function UserProfile({ userId }: { userId: string }) {
  const { data: user, isLoading, error } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetch(`/api/users/${userId}`).then(r => r.json()),
  });

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {(error as Error).message}</div>;
  return <div>{user?.name}</div>;
}
```

### Wrong: Migrating NgModules to React "module" wrappers

```typescript
// ❌ BAD — Trying to recreate Angular's NgModule concept in React
// shared.module.tsx
export function SharedModule({ children }: { children: React.ReactNode }) {
  return (
    <ThemeProvider>
      <AuthProvider>
        <HttpProvider>
          <ToastProvider>
            {children}
          </ToastProvider>
        </HttpProvider>
      </AuthProvider>
    </ThemeProvider>
  );
}
// This leads to deeply nested provider trees ("provider hell")
```

### Correct: Use flat composition with co-located providers

```typescript
// ✅ GOOD — Only wrap what needs wrapping, keep providers flat
// app/providers.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient();

export function AppProviders({ children }: { children: React.ReactNode }) {
  return (
    <QueryClientProvider client={queryClient}>
      {children}
    </QueryClientProvider>
  );
}

// Auth context only wraps routes that need it:
// <Route element={<AuthGuard />}>
//   <Route path="/dashboard" element={<Dashboard />} />
// </Route>
```

### Wrong: Keeping two-way data binding mentality with uncontrolled inputs

```typescript
// ❌ BAD — Using refs to imperatively read/write form values (Angular [(ngModel)] muscle memory)
function EditProfile() {
  const nameRef = useRef<HTMLInputElement>(null);
  const emailRef = useRef<HTMLInputElement>(null);

  const handleSubmit = () => {
    const name = nameRef.current!.value;   // Imperative read
    const email = emailRef.current!.value;  // No validation, no re-render
    fetch('/api/profile', { method: 'PUT', body: JSON.stringify({ name, email }) });
  };

  return (
    <form>
      <input ref={nameRef} defaultValue="John" />
      <input ref={emailRef} defaultValue="john@example.com" />
      <button type="button" onClick={handleSubmit}>Save</button>
    </form>
  );
}
```

### Correct: Use controlled components with state

```typescript
// ✅ GOOD — Controlled inputs: React state is the single source of truth
function EditProfile() {
  const [name, setName] = useState('John');
  const [email, setEmail] = useState('john@example.com');
  const [saving, setSaving] = useState(false);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setSaving(true);
    await fetch('/api/profile', {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ name, email }),
    });
    setSaving(false);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input value={name} onChange={e => setName(e.target.value)} />
      <input value={email} onChange={e => setEmail(e.target.value)} />
      <button type="submit" disabled={saving}>{saving ? 'Saving...' : 'Save'}</button>
    </form>
  );
}
```

### Wrong: Big-bang rewrite of the entire Angular app at once

```typescript
// ❌ BAD — Halting all feature development for 6+ months to rewrite
// "We'll rewrite the entire app in React over Q2-Q3, then launch."
// Result: scope creep, feature freeze frustration, missed deadlines,
// regressions discovered only after go-live.
```

### Correct: Strangler Fig -- migrate route by route while shipping features

```typescript
// ✅ GOOD — Incremental migration with continuous delivery
// Month 1: Set up single-spa shell, migrate /login route to React
// Month 2: Migrate /users route, keep shipping Angular features on /dashboard
// Month 3: Migrate /dashboard
// Month 4: Migrate /settings, remove Angular
// Each migration is tested independently and deployed to production.
```

## Common Pitfalls

- **Trying to find 1:1 React equivalents for every Angular concept**: Angular has DI, modules, decorators, lifecycle hooks, pipes, guards, interceptors, and resolvers. React has components, hooks, and context. Don't port the architecture -- port the behavior. [src3]
- **Keeping RxJS alongside React hooks**: Developers often keep RxJS "because it works" and subscribe inside `useEffect`. This creates dual reactivity systems, makes debugging harder, and prevents React's Suspense/Concurrent features from working. Fix: Replace `Observable`-based data fetching with React Query or SWR; replace `BehaviorSubject` state with `useState`/`useReducer`/Zustand. [src4]
- **Losing TypeScript strictness during migration**: Angular projects typically use `strict: true` in tsconfig. When creating the React project, ensure the same strictness. Fix: Copy `strict`, `noImplicitAny`, `strictNullChecks` settings to the React tsconfig. [src6]
- **Not handling zone.js removal properly**: Angular relies on `zone.js` for change detection. When running both frameworks, zone.js patches all async APIs (setTimeout, Promise, fetch), which can cause unexpected behavior in React. Fix: Configure Angular to run with `NgZone: 'noop'` during coexistence, or isolate Angular's zone to its micro-frontend. [src5]
- **Forgetting to migrate route guards and interceptors**: Angular's `canActivate` guards and `HttpInterceptor` don't have direct React equivalents. Fix: Implement guards as wrapper/layout route components; implement interceptors as Axios/fetch middleware. [src4]
- **Sharing styles without a strategy**: Angular components use ViewEncapsulation (emulated Shadow DOM). React components use global CSS, CSS modules, or CSS-in-JS. Mix them carelessly and styles bleed across frameworks. Fix: Use CSS Modules or a shared design token system (CSS custom properties) for both. [src3, src6]
- **Not measuring bundle size during coexistence**: Running both Angular (~150 KB gzipped) and React (~45 KB gzipped) simultaneously inflates the initial bundle. Fix: Use lazy loading aggressively -- only load Angular for legacy routes and React for migrated routes. Monitor with `webpack-bundle-analyzer`. [src7]
- **Ignoring Angular signals as a migration stepping stone**: Angular 16+ signals (`signal()`, `computed()`, `effect()`) are conceptually identical to React's `useState`/`useMemo`/`useEffect`. Refactoring RxJS to signals first makes the final React migration almost mechanical. [src2]

## Diagnostic Commands

```bash
# Count Angular-specific patterns remaining in codebase
grep -rn "@Component\|@Injectable\|@NgModule\|@Pipe\|@Directive" --include="*.ts" src/ | wc -l

# Count RxJS usage (should trend toward zero as migration progresses)
grep -rn "subscribe(\|\.pipe(\|BehaviorSubject\|switchMap\|mergeMap\|combineLatest" --include="*.ts" src/ | wc -l

# Count Angular signals (useful if pre-migrating RxJS to signals first)
grep -rn "signal(\|computed(\|effect(" --include="*.ts" src/ | wc -l

# Check total bundle size during coexistence
npx webpack-bundle-analyzer dist/stats.json

# Verify no zone.js interference in React components
grep -rn "zone.js\|NgZone" --include="*.ts" src/ | head -10

# List all Angular packages still installed
npm ls 2>/dev/null | grep @angular

# Check for duplicate dependencies (Angular + React both pulling in rxjs, etc.)
npm ls --all 2>/dev/null | grep -E "rxjs|zone.js|@angular" | sort -u

# Verify React routes are loading (browser console)
# window.__SINGLE_SPA_DEVTOOLS && console.log(window.__SINGLE_SPA_DEVTOOLS.exposedMethods.getAppNames())
```

## Version History & Compatibility

| Version | Status | Breaking Changes | Migration Notes |
|---|---|---|---|
| Angular 22 (~Jun 2026) | Upcoming | Signal-first era — zoneless change detection is the default for new projects | Closest yet to React's model; signals and zoneless remove most of the conceptual gap |
| Angular 21 (Nov 2025) | Current LTS | Continued signal/zoneless polish, Material 3 alignment | Modern baseline; minimal NgModule surface, signals everywhere |
| Angular 20 (May 2025) | LTS (security to Nov 2026) | `effect`, `linkedSignal`, `toSignal` stable; zoneless stable as of 20.2; incremental hydration | Recommended upgrade target before migrating -- signals map 1:1 to React hooks |
| Angular 19 (Nov 2024) | Maintenance | Standalone components default, `@let` syntax | Easy to migrate -- no NgModule boilerplate |
| Angular 17-18 (2023-24) | EOL/Maintenance | New control flow (`@if`, `@for`, `@switch`), deferrable views, zoneless (experimental) | Control flow closer to JSX; upgrade to 20+ first if time allows |
| Angular 14-16 (2022-23) | EOL/Maintenance | Standalone (opt-in), typed forms, signals (preview) | Standalone components migrate individually; refactor RxJS to signals first |
| React 19.2 (Oct 2025) | Current | Document metadata in components, refined Suspense/Activity, type hardening | Latest stable (19.2.x). Use with the React Compiler |
| React Compiler v1.0 (Oct 2025) | Stable | Automatic memoization | Drop most manual `useMemo`/`useCallback`; opt in via the babel/swc plugin + `eslint-plugin-react-hooks` v7 |
| React 19 (Dec 2024) | Active | `use()` hook, `ref` as prop, Actions, `useActionState`, `useOptimistic`; `forwardRef` deprecated | Use modern API -- pass `ref` as a prop, prefer Actions for forms |
| React 18 (Mar 2022) | LTS | `createRoot`, Concurrent features, `useSyncExternalStore` | Minimum recommended React version for new migrations |

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Team is hiring React developers and Angular talent is scarce | Angular team is productive and no hiring issues | Stay on Angular, upgrade to latest version |
| App needs React ecosystem libraries (Next.js, React Native, Remix) | Server-side rendering is the only need | Angular 17+ SSR or Angular Universal |
| Codebase has accumulated technical debt and a rewrite is justified | App is well-maintained and meeting business needs | Upgrade Angular version instead |
| Company is standardizing on React across multiple products | Angular app is isolated and self-contained | Maintain independently |
| Complex RxJS chains are causing bugs and developer confusion | Team is proficient with RxJS and reactive patterns | Simplify RxJS with Angular signals instead |
| UI library (Angular Material) is limiting design flexibility | Current UI library meets design requirements | Swap UI library within Angular |
| Need to share code between web and mobile (React Native) | Only need responsive web design | Keep Angular, use responsive CSS |

## Important Caveats

- Angular and React have fundamentally different mental models: Angular is opinionated (DI, decorators, modules, RxJS), React is flexible (hooks, JSX, pick-your-own-libraries). Resist the urge to recreate Angular's architecture in React -- embrace React's patterns.
- During coexistence (single-spa / Module Federation), expect a 150-200 KB increase in initial bundle size. Use aggressive code splitting and lazy loading to keep perceived performance acceptable.
- zone.js (Angular's change detection mechanism) patches global async APIs. If React components exhibit unexpected re-renders or timing issues during coexistence, configure Angular with `NgZone: 'noop'` or isolate its zone.
- RxJS `Observable` patterns do not compose well with React Suspense or React Server Components. Plan to fully replace RxJS with React-native patterns (React Query, hooks, `useSyncExternalStore`) rather than bridging them.
- Angular 20+ (signals stable, zoneless stable as of 20.2) and the upcoming Angular 22 "signal-first" release are architecturally much closer to React. If your Angular app is on v14-19, upgrading to v20+ first and refactoring RxJS to signals usually shrinks the eventual React migration, because signals map almost 1:1 to React hooks.
- React 19's `use()` hook, `useActionState`, and `useOptimistic` provide first-class replacements for patterns Angular developers are accustomed to (async pipe, form state management, optimistic updates). Prefer these over older patterns when targeting React 19.2.
- The React Compiler reached v1.0 (Oct 2025) and provides automatic memoization. Adopt it on the React side of the migration to avoid hand-writing `useMemo`/`useCallback` — pair it with `eslint-plugin-react-hooks` v7 for compiler-aware linting. Note `forwardRef` is deprecated in React 19; pass `ref` as a regular prop in any plugin/component wrappers.
- Timeline estimates vary significantly: small apps (<10K LOC) take 1-3 months; medium apps (10K-50K LOC) take 3-6 months; large enterprise apps (>50K LOC) can take 6-18 months with the Strangler Fig approach.

## Related Units

- [AngularJS to Angular Migration](/software/migrations/angularjs-to-angular/2026)
- [AngularJS to React Migration](/software/migrations/angularjs-to-react/2026)
- [jQuery to React Migration](/software/migrations/jquery-to-react/2026)
- [JavaScript to TypeScript Migration](/software/migrations/javascript-to-typescript/2026)
