Nefe James Nefe is a frontend developer who enjoys learning new things and sharing his knowledge with others.

The guide to converting Angular apps to React

12 min read 3508

Converting Angular apps to React has become popular as developers seek smaller and speedier projects. They are two of the most popular JavaScript frontend frameworks out there, but as projects grow in size, functionality, and complexity, it can become necessary to migrate from one framework to another.

Migrating will give your engineering team the opportunity to update your projects to the standards expected of modern apps and better react to the ever-changing facets (primarily in terms of maintenance) of the software development lifecycle, and migrating from Angular to React is no exception to this.

In this article, you will learn about the considerations you should make when converting Angular apps to React, migration strategies, the advantages and disadvantages of migrating, why you may want to migrate; and then finally we will do a walkthrough of the process of converting an Angular app to React, which you can follow along with at your own pace. Let’s get started.

Jump ahead:

Things to note when migrating from Angular to React

Angular and React are great frontend frameworks with distinct characteristics that make them fundamentally different, and you must take note of the following when migrating:

  • React uses a virtual DOM, while Angular works with a real DOM
  • Angular has two-way binding, while React has one-way binding
  • Angular comes with TypeScript out of the box; React does not
  • Angular has built-in support for AJAX, HTTP, and Observables, and React does not.
  • React has a large ecosystem and direct support from Meta (Facebook), while Angular is only supported by the developer community
  • Angular comes with out-of-the-box features like validation, component-scoped CSS, animations, conditional rendering, and more
  • The Angular CLI enables easy generation of components, modules, and other features that help developer productivity

What to do before migrating Angular to React

Set goals to avoid scope creep

Many development teams suffer the consequences of not laying down the scope when working on projects.

What is the goal and focus of this migration? Will it strictly be a one-and-done migration, or will you use the opportunity to make updates, bug fixes, and performance optimizations along the way? Note that the answers to these questions will affect the delivery timeline of the migration.

Carry out an audit to understand pain points and blockers

What limitations and inefficiencies of your current system require a switch? It is important that the new stack you are switching to removes existing blockers without adding new ones. Ensure that there is a valid reason to migrate before you do so and you’re not carrying over issues.

Choose the right migration strategy

What migration strategy will you use? Will you stagger the migration release? Choosing the right strategy is key to a successful migration and maintaining expectations.

Some reasons teams may migrate from Angular to React

The needs of businesses and development teams change over time, and sometimes those changes require a transition of tech stacks to adapt.

Here are the key reasons you may migrate from Angular to React:

  • The adoption of React is continually increasing among developers and new crops of web development talents focus on learning the skills with the greatest market demand. This means that it will become increasingly difficult to find or hire Angular developers to manage projects and you may need to switch to React as a matter of necessity
  • React has a broader range of flexibility than Angular — you can easily find blog templates, animation libraries, multiple component libraries, toast libraries, and more. Compared to Angular, React has a larger and more active open source community that continually develops third-party libraries, templates, online courses, and other resources
  • Business needs and focuses change over time, and developers may be required to build mobile apps to serve more customers. It will be easier and faster for them to adopt a React codebase to work with React Native

Migration strategies

Migrating an app’s codebase is complex, and while there is no easy way to do this, there are several strategies we can explore.

Rewrite

A complete rewrite from the ground up is considered a better way of migrating applications in terms of quality because you get to start with a new application, define an architecture that fits the new framework, and there is no need to deal with integrating old and new code.

However, this strategy has drawbacks — it requires more time and resources, and the delivery timeline can be challenging to estimate.



Strangler pattern

React and Angular are component-based frameworks and you can take advantage of this when defining your migration strategy. Component-based migration means you can break your Angular app into individual parts and migrate them one after the other.

Since the development of different components can progress in parallel, you can have separate teams migrating different parts of the app — with this strategy, you can stagger releases and deploy the migration for each component.

A disadvantage is that you will have to integrate the new framework’s architecture with that of the old framework.

Why you may not want to migrate from Angular to React

An enduring benefit of Angular is that it makes decision making easier because of its opinionated system and built-in tools — developers don’t need to spend days and weeks meeting to discuss the libraries for state management or data fetching.

Angular is built with TypeScript, and working with TypeScript out-the-box reinforces clean code and makes debugging and maintenance easier down the line — scaling issues aside.

Moving from Angular to React will mean losing out on the Angular CLI, which developers can use to create repeatable blocks of code like components, services, pipes, and directives from the command line.

Converting an Angular app to React

Having looked at the background involved in framework migration, let’s convert an Angular app to React and see how it’s done.


More great articles from LogRocket:


We will use Next.js for the React app; however, the same principles and code snippets can be adopted into a vanilla React app.

To get started, follow the steps in these Angular and Next.js guides to spin up new Angular and React applications.

The project we will convert

We will convert a simple task tracker app from Angular to React; the image below shows the app.

When the Add button is clicked, it reveals a modal where we can add new tasks.

Task tracker

Adding the task in task tracker

These are the different components the app is made up of:

  • Header: The app’s header
  • Button: A reusable button component
  • Add Task: A form component responsible for adding new tasks
  • Task Item: The individual task item
  • Tasks: The component where we render all the tasks

Let’s start creating the components. We will look at the Angular code first, and then convert that to React.

(Note: Working knowledge of Angular and Next.js is required to follow along with the rest of this article)

Defining the Angular services

Services are blocks of functionality an app needs to complete a specific task or to carry out an operation. Services are a critical part of adding functionality to Angular apps; learn more about them here.

There are two services the Angular application will need, a Task service and a UI service.

Creating the Task service

This will be in charge of fetching tasks from a mock API, adding new tasks, and deleting tasks.

The code below does the following:

  • getTasks fetches the tasks
  • deleteTask adds new tasks
  • addTask deletes tasks
//

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { Task } from 'src/app/mock-tasks';
import { HttpClient, HttpHeaders } from '@angular/common/http';

const httpOptions = {
  headers: new HttpHeaders({
    'Content-Type': 'application/json',
  }),
};

@Injectable({
  providedIn: 'root',
})

export class TaskService {
  private apiUrl = 'http://localhost:5000/tasks';
  constructor(private http: HttpClient) {}
 
  getTasks(): Observable<Task[]> {
    return this.http.get<Task[]>(this.apiUrl);
  }
 
  deleteTask(task: Task): Observable<Task> {
    const url = `${this.apiUrl}/${task.id}`;
    return this.http.delete<Task>(url);
  }
 
  addTask(task: Task): Observable<Task> {
    return this.http.post<Task>(this.apiUrl, task, httpOptions);
  }
}

 

Creating the UI Service

The UI service will control the form-toggling functionality via the toggleAddTask function.

//

import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';

@Injectable({
  providedIn: 'root',
})

export class UiService {
  private showAddTask: boolean = false;
  private subject = new Subject<any>();
  constructor() {}
  toggleAddTask(): void {
    this.showAddTask = !this.showAddTask;
    this.subject.next(this.showAddTask);
  }
  onToggle(): Observable<any> {
    return this.subject.asObservable();
  }
}

Great! Now that we’ve defined the services, now we move on to create the components.

Creating the header component

The header component will contain a button, which, when clicked, will toggle the form the user will use to add more tasks. In addition, the background color and text of the button will also be toggled.

To set this up in Angular, create a header component in the app and do the following:

First, define the HTML for the component.

<header>
  <h1>Task Tracker</h1>
  <app-button
    color="{{ showAddTask ? 'red' : 'green' }}"
    text="{{ showAddTask ? 'Close' : 'Add' }}"
    (btnClick)="toggleAddTask()"
  ></app-button>
</header>

The button accepts a btnClick handler and the toggleAddTask method is responsible for toggling the form’s visibility.

Hook up to the service and create the toggleAddTask method.

Then, add the code to define the behavior:

import { Component, OnInit } from '@angular/core';
import { Subscription } from 'rxjs';
import { UiService } from 'src/app/services/ui.service';

@Component({
  selector: 'app-header',
  templateUrl: './header.component.html',
  styleUrls: ['./header.component.css'],
})

export class HeaderComponent implements OnInit {
  showAddTask: boolean = false;
  subscription: Subscription;

  constructor(private uiService: UiService) {
    this.subscription = this.uiService
      .onToggle()
      .subscribe((value) => (this.showAddTask = value));
  }
  ngOnInit(): void {}
  ngOnDestroy() {
    // Unsubscribe to ensure no memory leaks
    this.subscription.unsubscribe();
  }
  toggleAddTask() {
    this.uiService.toggleAddTask();
  }
}

We access the UI service containing a toggleAddTask and use that to define a second toggleAddTask method, which we used in the header’s HTML.

We can see that there are quite a number of moving parts when it comes to setting up functionality for Angular. Let’s see how this works in React.

First, create a header component and paste in the code below.

import Button from "../Button/Button";
import styled from "styled-components";
import { useShowFormContext } from "../../context/showFormContext";

export default function Header() {
  const { showAddTaskForm, toggleAddTaskForm } = useShowFormContext();
  return (
    <StyledHeader>
      <h1>Task Tracker</h1>
      <Button
        bgColor={showAddTaskForm ? "red" : "green"}
        btnClickHandler={toggleAddTaskForm}
        btnLabel={showAddTaskForm ? "Close" : "Add"}
      />
    </StyledHeader>
  );
}

Here, we access the showAddTaskForm boolean and the toggleAddTaskForm functions from showFormContext and use them to set up the functionality. Next, we create the context.

Create the showFormContext, which will contain showAddTaskForm and toggleAddTaskForm.

import { useState, useContext, createContext } from "react";
const ShowFormContext = createContext();
export const useShowFormContext = () => useContext(ShowFormContext);

export default function ShowFormContextProvider({ children }) {
  const [showAddTaskForm, setShowAddTaskForm] = useState(false);
  const toggleAddTaskForm = () => {
    setShowAddTaskForm(!showAddTaskForm);
  };
  return (
    <ShowFormContext.Provider value={{ showAddTaskForm, toggleAddTaskForm }}>
      {children}
    </ShowFormContext.Provider>
  );
}

We’ve set up the same component with similar features but with fewer lines of code — that’s a win for React and the simplicity it gives us.

Creating the button component

This button is not to be mistaken for the button on the form. Rather, it is the button we used in the header component. Let’s start with Angular.

Create the button’s HTML:

<button 
  class="btn" 
  (click)="onClick()"
  [ngStyle]="{ 'background-color': color }">
  {{ text }}
</button>

Set up the button’s functionality:

import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-button',
  templateUrl: './button.component.html',
  styleUrls: ['./button.component.css'],
})

export class ButtonComponent implements OnInit {
  @Input() text: string = '';
  @Input() color: string = '';
  @Output() btnClick = new EventEmitter();
  constructor() {}
  ngOnInit(): void {}
  onClick() {
    this.btnClick.emit();
  }
}

The color and text of the button are dynamic, so we set up variables for them. Furthermore, the button needs to accept a click event, so we emit the btnClick.

Note how much more complex it is to define props and pass dynamic data to components in Angular.

Let’s set up the button in React and see how it looks:

import Styled from "styled-components";
export default function Button({ btnLabel, bgColor, btnClickHandler }) {
  return (
    <StyledButton onClick={btnClickHandler} bgColor={bgColor}>
      {btnLabel}
    </StyledButton>
  );
}

This is all it takes! Clearly, Angular’s version of ‘props’ has a higher level of complexity.

Creating the Add Task Component

The Add Task component is the form we use to add new tasks. Let’s set it up in Angular.

Here’s how we define the HTML skeleton:

<form *ngIf="showAddTask" class="add-form" (ngSubmit)="onSubmit()">
  <div class="form-control">
    <label for="text">Task</label>
    <input
      type="text"
      name="text"
      [(ngModel)]="text"
      id="text"
      placeholder="Add Task"
    />
  </div>
  <div class="form-control">
    <label for="day">Day & Time</label>
    <input
      type="text"
      name="day"
      [(ngModel)]="day"
      id="day"
      placeholder="Add Day & Time"
    />
  </div>
  <input type="submit" value="Save Task" class="btn btn-block" />
</form>

Here, we do the following:

  • Define the HTML and use Angular’s ngModel form directive to track the value of the form fields
  • Conditionally render the form with the *ngIf directive based on the state of showAddTask

Make the form interactive

Next, we need to add functionality to the form by defining the variables the data will be passed to and handling the form submission:

import { Component, OnInit, Output, EventEmitter } from '@angular/core';
import { UiService } from 'src/app/services/ui.service';
import { Subscription } from 'rxjs';
import { Task } from 'src/app/mock-tasks';
@Component({
  selector: 'app-add-task',
  templateUrl: './add-task.component.html',
  styleUrls: ['./add-task.component.css'],
})
export class AddTaskComponent implements OnInit {
  @Output() onAddTask: EventEmitter<Task> = new EventEmitter();
  text: string;
  day: string;
  showAddTask: boolean;
  subscription: Subscription;
  constructor(private uiService: UiService) {
    this.subscription = this.uiService
      .onToggle()
      .subscribe((value) => (this.showAddTask = value));
  }
  ngOnInit(): void {}
  ngOnDestroy() {
    // Unsubscribe to ensure no memory leaks
    this.subscription.unsubscribe();
  }
  onSubmit() {
    const newTask: Task = {
      text: this.text,
      day: this.day,
    };
    this.onAddTask.emit(newTask);
    this.text = '';
    this.day = '';
  }
}

Here, we create the onSubmit function, which adds new tasks and clears the form field. We also add the necessary variables that will hold the form data.

Let’s set up the same thing in React:

const handleSubmit = (e) => {
  e.preventDefault();
  const formData = new FormData(e.target);
  const data = Object.fromEntries(formData);
  console.log(data);
};

export default function AddTask() {
  return (
    <StyledForm className="add-form" onSubmit={handleSubmit}>
      <div className="form-control">
        <label for="text">Task</label>
        <input type="text" name="task" id="text" placeholder="Add Task" />
      </div>
      <div className="form-control">
        <label for="day">Day & Time</label>
        <input type="text" name="day" id="day" placeholder="Add Day & Time" />
      </div>
      <input type="submit" value="Save Task" className="btn btn-block" />
    </StyledForm>
  );
}

Let’s break down the code above:

  • First, we define the HTML
  • Next, we set up the handleSubmit function, which leverages the FormData constructor to access the form’s data

Creating the task item and task components

The task item component is each task that is performed. It contains the task’s title, date, and a button for deleting the task.

Let’s set it up in Angular:

<div class="task">
  <h3>
    {{ task.text }}
    <fa-icon
      [icon]="faTimes"
      [ngStyle]="{ color: 'red' }"
      (click)="onDelete(task)"
    ></fa-icon>
  </h3>
  <p>{{ task.day }}</p>
</div> 

We need to create the onDelete method that we passed to the icon.

import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { Task } from 'src/app/mock-tasks';
import { faTimes } from '@fortawesome/free-solid-svg-icons';

@Component({
  selector: 'app-task-item',
  templateUrl: './task-item.component.html',
  styleUrls: ['./task-item.component.css'],
})

export class TaskItemComponent implements OnInit {
  @Input() task: Task;
  faTimes = faTimes;
  @Output() onDeleteTask = new EventEmitter<Task>();
  constructor() {}
  ngOnInit(): void {}
  onDelete(task: Task) {
    this.onDeleteTask.emit(task);
  }
}

Here, we define the onDelete method and use that to omit the onDeleteTask method, so the Task Item’s parent can catch the delete event and remove the task.

Let’s create the Tasks component to see what that looks like.

<app-add-task (onAddTask)="addTask($event)"></app-add-task>
<app-task-item
  *ngFor="let task of tasks"
  [task]="task"
  (onDeleteTask)="deleteTask(task)"
></app-task-item>

Here, we define the HTML for the Tasks component. There, we loop through the array of tasks and render each task in the Task Item component.

Then, we catch the onDeleteTask delete event from the Task component and use that to set up the task deletion functionality. We also do the same thing for the Add Task component and the onAddTask event.

Finally, we utilize the functionality of the Tasks service:

import { Component, OnInit } from '@angular/core';
import { Task } from 'src/app/mock-tasks';
import { TaskService } from 'src/app/services/task.service';

@Component({
  selector: 'app-tasks',
  templateUrl: './tasks.component.html',
  styleUrls: ['./tasks.component.css'],
})

export class TasksComponent implements OnInit {
  tasks: Task[] = [];
  constructor(private taskService: TaskService) {}
  ngOnInit(): void {
    this.taskService.getTasks().subscribe((tasks) => (this.tasks = tasks));
  }
  deleteTask(task: Task) {
    this.taskService
      .deleteTask(task)
      .subscribe(
        () => (this.tasks = this.tasks.filter((t) => t.id !== task.id))
      );
  }
  addTask(task: Task) {
    this.taskService
      .addTask(task)
      .subscribe((task) => this.tasks.unshift(task));
  }
}

Here, we access the addTask and deleteTask functions from the service and use them to create another set of addTask and deleteTask methods that we used in the Tasks and Task Item components.

Then, we fetched the list of tasks from the mock API using the getTasks function.

That’s it for Angular! Now, let’s migrate the Tasks and Task Item functionality to React, starting with Task Item:

export default function TaskItem({ task, tasks, setTasks }) {
  const deleteTask = () => {
    let newTasks;
    fetch(`http://localhost:5000/tasks/${task.id}`, {
      method: "DELETE",
    })
      .then((newTasks = tasks.filter((t) => t.id !== task.id)))
      .then(setTasks(newTasks));
    console.log("red");
  };
  return (
    <Container className="task">
      <h3>
        {task.text} <DeleteIcon const deleteTask={deleteTask} />
      </h3>
      <p>{task.day}</p>
    </Container>
  );
}

function DeleteIcon({ deleteTask }) {
  return (
    <svg
      onClick={deleteTask}
    >
      <path d=""></path>
    </svg>
  );
}

Here, we defined a deleteTask function and passed it to the icon. Then we also rendered the data for the task.

For the Tasks component:

import AddTask from "../AddTask/AddTask";
import TaskItem from "../TaskItem/TaskItem";
import { useShowFormContext } from "../../context/showFormContext";
import { useState, useEffect } from "react";

export default function Tasks() {
  const [tasks, setTasks] = useState(null);
  const { showAddTaskForm } = useShowFormContext();

  useEffect(() => {
    fetch("http://localhost:5000/tasks")
      .then((res) => res.json())
      .then((data) => setTasks(data));
  }, []);

  return (
    <>
      {showAddTaskForm && <AddTask />}
      {tasks
        ? tasks.map((task) => (
            <TaskItem
              key={task.id}
              task={task}
              tasks={tasks}
              setTasks={setTasks}
            />
          ))
        : null}
    </>
  );
}

Here, we did the following:

  • Fetched the tasks from the mock API
  • Toggled the visibility of the form based on the value of showAddTaskForm
  • Rendered the list of tasks

With that, we have successfully converted a basic task tracker app from Angular to React.

However, note that this was a basic project and that migration will be more complex when dealing with Angular apps with multiple services, complicated state management, and many pages and features — but the principle remains the same.

Insights from the conversion process

  • While Angular and React are both great frontend frameworks, the former is more complex than the latter
  • On average, it takes significantly more lines of code to create a feature in Angular than it does in React
  • Overall, converting components from Angular to React results in fewer lines of code and makes things simpler and faster. This improves app performance and provides a better maintenance and developer experience moving forward

Conclusion

Migrating can be a rather daunting task that takes a lot of effort, time, planning, and development to ensure it goes smoothly without compromising maintenance and development speed. It is important to consider the pros and cons before migrating.

While there are many benefits of converting with a total rewrite, it isn’t always possible because the majority of companies don’t have the time or resources to commit to this method. In such cases, we can leverage tools that automate a large chunk of the migration process, such as ngReact, react2angular, and angular2react.

I hope you found this explainer and tutorial article useful — you can get the full source code for the Angular and React app from this repo.

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

Nefe James Nefe is a frontend developer who enjoys learning new things and sharing his knowledge with others.

Leave a Reply