From React to Angular: A Step-by-Step Angular Guide for React Developers
/ 11 min read
Table of Contents
Learning Angular for React Developers
If you’re a React developer wanting to learn Angular, or an Angular developer wanting to enter the React world, you’re probably thinking: “How hard can it be?” The good news is that many fundamental concepts are shared, but Angular has a different approach, and understanding it can smooth your learning journey.
React is a flexible library that gives you freedom of action, while Angular is a complete framework with a defined structure. If you’re used to installing a new library for every need, you might be a bit surprised that Angular has everything prepared in advance!
In this article, instead of starting from scratch, we’ll use your React knowledge as a bridge and compare concepts one-by-one. We’ll explore:
- Fundamental differences between library and framework and how to set up a project
- Component structure: How we go from JSX functions to TypeScript classes
- State management: From
useStateto class variables and new Signals - Props and Data Flow: Differences between passing props and the
@Inputdecorator - Conditionals and loops: From
mapand&&to modern Angular Control Flow - Component lifecycle:
useEffectequivalents in Angular - Form management: Two-Way Binding with
[(ngModel)] - State management strategies: From Services and RxJS to NgRx
- A complete practical example: Todo List in React and Angular side-by-side
The goal isn’t to say which is better, but to transfer your mental model from React to Angular. So grab your tea and let’s start this journey together!
1. First Glance (Framework vs Library)
Key Philosophical Difference:
-
React: It’s a library. You need to install third-party libraries for routing, forms, and HTTP requests. React gives you building blocks and lets you choose your own tools (React Router, Axios, React Hook Form, etc.).
-
Angular: It’s a complete framework. Everything (Router, HTTP Client, Form Validation) is built-in. Angular is also heavily dependent on TypeScript - it’s not optional, it’s the default way to work.
Installing and Setting Up an Angular Project
To install and run an Angular project, follow these steps in order:
Prerequisites: Node.js must be installed on your system.
Step 1 - Install Angular CLI
The Angular command-line tool (CLI) is essential for building, managing, and running projects. Install it globally:
npm install -g @angular/cliStep 2 - Create a New Project
After installing the CLI, you can create a new project:
ng new my-projectThe CLI will ask you configuration questions (routing, styling format, etc.)
Step 3 - Navigate to Project Folder
cd my-projectStep 4 - Run and View the Project
To compile and run the project on a local server:
ng serve --openThe --open flag automatically opens your browser to http://localhost:4200
2. Component Structure
Core Difference in Architecture:
In React, a component is a function that returns JSX. In Angular, a component is a class marked with a decorator, and the template (HTML) and logic (TS) are usually separate (though they can be inline).
React Approach:
import { useState } from "react";
function App() { return <h1>Hello React!</h1>;}export default App;Angular Approach:
import { Component } from "@angular/core";
@Component({ selector: "app-root", // CSS selector for this component standalone: true, // Modern standalone component (Angular 14+) template: `<h1>Hello Angular!</h1>`, // Inline template // OR: templateUrl: './app.component.html' for external file styleUrls: ["./app.component.css"],})export class AppComponent { // Application logic goes here}- The
@Componentdecorator provides metadata about the component selectoris how you use this component in HTML:<app-root></app-root>standalone: trueis the modern way (Angular 14+) that doesn’t require NgModules- You can write templates inline or in separate HTML files
3. State Management
In React, you use the useState hook. In classic Angular, class variables are the state, and Angular automatically detects changes through its change detection mechanism.
(Note: In newer Angular versions, there’s a concept called Signals that’s similar to useState, but we’ll first look at the classic approach).
React Approach:
function Counter() { const [count, setCount] = useState(0);
const increment = () => { setCount(count + 1); };
return <button onClick={increment}>Count: {count}</button>;}Angular Approach:
@Component({ selector: "app-counter", standalone: true, template: ` <button (click)="increment()">Count: {{ count }}</button> `,})export class CounterComponent { count = 0; // This IS the state
increment() { this.count++; // Direct mutation is allowed! }}Key Differences Explained:
-
Variable Display:
- React:
{value}(single curly braces in JSX) - Angular:
{{value}}(double curly braces - called interpolation)
- React:
-
Event Binding:
- React:
onClick(camelCase attribute) - Angular:
(click)(parentheses syntax for events)
- React:
-
Mutation Pattern:
- React: Immutable - must use
setStatefunction - Angular: Mutable - can directly change class properties (change detection handles it)
- React: Immutable - must use
4. Props / Input
In React, you receive props as function arguments. In Angular, you use the @Input() decorator to mark properties that can be passed from parent components.
React (Parent → Child):
// Parent<UserCard name="Ali" />;
// Childfunction UserCard({ name }) { return <p>User: {name}</p>;}Angular (Parent → Child):
// Parent Template (HTML)<app-user-card [name]="'Ali'"></app-user-card>// The square brackets [] indicate property binding
// Child Component (user-card.component.ts)import { Component, Input } from '@angular/core';
@Component({ selector: 'app-user-card', template: `<p>User: {{ name }}</p>`})export class UserCardComponent { @Input() name: string = ''; // Define input property}[name]with square brackets means “bind this property”- Without brackets
name="Ali"would pass the literal string “Ali” - With brackets
[name]="'Ali'"evaluates the expression and passes the value - The
@Input()decorator makes the property accessible from parent components
5. Loops and Conditionals
In React, you use map and JavaScript operators (&& or ? :). In modern Angular (v17+), there’s new “Control Flow” syntax that’s very readable.
Conditionals (Conditional Rendering):
React:
{ isAdmin && <button>Edit</button>;}Angular:
@if (isAdmin) {<button>Edit</button>}Loops (List Rendering):
React:
<ul> {users.map((user) => ( <li key={user.id}>{user.name}</li> ))}</ul>Angular:
<ul> @for (user of users; track user.id) { <li>{{ user.name }}</li> }</ul>- The
@ifand@forsyntax is Angular’s new built-in control flow (v17+) track user.idis like React’skeyprop - it helps Angular optimize rendering- Older Angular versions used
*ngIfand*ngFordirectives instead - The new syntax is cleaner and more performant
6. Lifecycle
You know useEffect. In Angular, there are specific methods in the class that serve similar purposes.
Lifecycle Mapping:
useEffect(() => {}, [])(Mount): Equivalent tongOnInitin AngularuseEffect(() => { return () => {} }, [])(Unmount): Equivalent tongOnDestroy
Angular Lifecycle Example:
import { Component, OnInit, OnDestroy } from "@angular/core";
export class ExampleComponent implements OnInit, OnDestroy { ngOnInit() { console.log("Component loaded - like useEffect with empty array"); // Good place for API calls }
ngOnDestroy() { console.log("Component is being destroyed - like cleanup function"); // Clean up subscriptions, timers, etc. }}- You must implement the interface (
implements OnInit) to use the lifecycle method - Other useful lifecycle hooks:
ngOnChanges()- when input properties change (like useEffect with dependencies)ngAfterViewInit()- after component’s view is fully initializedngDoCheck()- custom change detection
7. Form Management (Two-Way Binding)
In React, to connect an input to state, you need to write both value and onChange. Angular has a “magical” directive called [(ngModel)] that creates two-way binding.
React Approach:
<input value={name} onChange={(e) => setName(e.target.value)} />Angular Approach:
<!-- Requires importing FormsModule --><input [(ngModel)]="name" /><p>Hello {{ name }}</p>- When you type in the input, the
namevariable in the class automatically updates, and vice versa - The
[(ngModel)]syntax is called “banana in a box” 🍌📦 - You must import
FormsModuleto usengModel - This is conceptually similar to React’s controlled components but with less boilerplate
8. State Management Approaches in Angular
Unlike React where third-party libraries (like Redux or Zustand) are quickly added to projects, Angular’s framework has very powerful built-in tools. State management in Angular can be divided into three main levels:
1. Native Approach (Services and RxJS)
This is the most common method in medium and large projects. In React, you used Context API; in Angular, we use Services with RxJS.
- BehaviorSubject: A variable defined in a service that holds the state
- Observable: Components “subscribe” to this state to be notified of changes
Example Pattern:
import { Injectable } from "@angular/core";import { BehaviorSubject } from "rxjs";
@Injectable({ providedIn: "root" })export class StateService { private countSubject = new BehaviorSubject<number>(0); count$ = this.countSubject.asObservable();
increment() { this.countSubject.next(this.countSubject.value + 1); }}2. Using Signals (Modern Approach)
If you’re comfortable with useState in React, Signals (introduced in v16+) will be your best friend in Angular. Signals make state management much simpler, improve performance, and make code more readable.
- Writable Signals: Works like
useState - Computed Signals: Like
useMemofor state-dependent calculations - Effect: Like
useEffectfor running side effects when state changes
Example:
import { signal, computed, effect } from "@angular/core";
export class CounterComponent { count = signal(0); doubleCount = computed(() => this.count() * 2);
constructor() { effect(() => { console.log("Count changed:", this.count()); }); }
increment() { this.count.update((val) => val + 1); }}3. Global State Management (NgRx)
If you used Redux in React, NgRx is exactly equivalent. This library is built on Redux patterns and includes Actions, Reducers, and Effects.
- Suitable for: Very large projects with high complexity
- Note: Learning NgRx takes time due to significant boilerplate
Quick Comparison:
| Concept in React | Equivalent in Angular |
|---|---|
| useState / useReducer | Signals or Internal Component State |
| Context API | Services + RxJS (BehaviorSubject) |
| Redux / Zustand | NgRx or NGXS |
| useEffect | Signals Effect or RxJS Hooks |
This section requires detailed explanation, so it will be fully covered in the next part of this series. If you want it published sooner, please let me know in the comments!
9. Complete Code Example (Simple Todo List)
Let’s build a complete Todo List to see the different mental models side-by-side.
React Version (Functional with Hooks)
This is the code you’re probably familiar with:
import { useState } from "react";import "./App.css";
function TodoApp() { // 1. State Definition const [text, setText] = useState(""); const [todos, setTodos] = useState([ { id: 1, title: "Learn React", completed: true }, { id: 2, title: "Learn Angular", completed: false }, ]);
// 2. Logic Methods const addTodo = () => { if (!text.trim()) return; const newTodo = { id: Date.now(), title: text, completed: false };
setTodos([...todos, newTodo]); setText(""); };
const deleteTodo = (id) => { setTodos(todos.filter((t) => t.id !== id)); };
const toggleTodo = (id) => { setTodos( todos.map((t) => (t.id === id ? { ...t, completed: !t.completed } : t)), ); };
// 3. Template (JSX) return ( <div className="container"> <h2>My Todo List (React)</h2>
<div className="input-group"> <input value={text} onChange={(e) => setText(e.target.value)} placeholder="New task..." /> <button onClick={addTodo}>Add</button> </div>
<ul> {todos.map((todo) => ( <li key={todo.id}> <span onClick={() => toggleTodo(todo.id)} style={{ textDecoration: todo.completed ? "line-through" : "none", }} > {todo.title} </span> <button onClick={() => deleteTodo(todo.id)}>Delete</button> </li> ))} </ul> </div> );}
export default TodoApp;Angular Version
In Angular, files are usually separate (TS, HTML, CSS), but here for simplicity, everything is shown in one file.
import { Component } from "@angular/core";import { FormsModule } from "@angular/forms"; // 1. Required for [(ngModel)]
// Interface for TypeScript (optional but recommended)interface Todo { id: number; title: string; completed: boolean;}
@Component({ selector: "app-todo", standalone: true, imports: [FormsModule], // Import required modules in the component itself template: ` <div class="container"> <h2>My Todo List (Angular)</h2>
<div class="input-group"> <!-- 3. Two-Way Binding: text value updates simultaneously in UI and class --> <input [(ngModel)]="text" placeholder="New task..." (keydown.enter)="addTodo()" /> <button (click)="addTodo()">Add</button> </div>
<ul> <!-- 4. Control Flow: new loop syntax --> @for (todo of todos; track todo.id) { <li> <span (click)="toggleTodo(todo)" [style.text-decoration]="todo.completed ? 'line-through' : 'none'" class="todo-text" > {{ todo.title }} </span> <button (click)="deleteTodo(todo.id)">Delete</button> </li> } @empty { <p>No tasks exist!</p> } </ul> </div> `, styles: [ ` .todo-text { cursor: pointer; margin-right: 10px; } `, ],})export class TodoComponent { // State Definition (simple class variables) text: string = ""; todos: Todo[] = [ { id: 1, title: "Learn React", completed: true }, { id: 2, title: "Learn Angular", completed: false }, ];
// Logic Methods addTodo() { if (!this.text.trim()) return;
const newTodo: Todo = { id: Date.now(), title: this.text, completed: false, };
this.todos.push(newTodo); // Direct mutation allowed! this.text = ""; // Clear input by changing variable }
deleteTodo(id: number) { // We can use splice or filter and reassign to this.todos this.todos = this.todos.filter((t) => t.id !== id); }
toggleTodo(todo: Todo) { // We have the object reference, change it directly! // No need to map the entire array todo.completed = !todo.completed; }}Key Differences Highlighted:
-
State Updates: React requires immutability (
setTodos([...todos, newTodo])), Angular allows direct mutation (this.todos.push(newTodo)) -
Event Handling: React uses camelCase (
onClick), Angular uses parentheses syntax(click) -
Conditional Rendering: React uses JSX expressions, Angular has built-in
@emptyblock for empty states -
Two-Way Binding: React needs
value+onChange, Angular has[(ngModel)] -
Styling: Both support inline styles, but syntax differs (
style={{}}vs[style.property])
10. Conclusion
If you’ve stayed with me this far, congratulations! You now have a general understanding of the differences between Angular and React, and you know that transitioning from one to the other isn’t as difficult as you might have thought.
As mentioned at the beginning of the article, if you want the advanced State Management section (Services, RxJS, and NgRx) published sooner, please let me know in the comments so it can be prioritized.
Good luck! 🚀
What is your opinion?