✅ What Does Reactive Mean?

At its core, reactive programming is about writing code that automatically reacts to changes in data or events.

Instead of manually checking for changes or polling data, reactive code updates itself when something it depends on changes.


🧠 Think of It Like This:

  • You define data sources (signals, observables).
  • You define reactions (functions that run when data changes).
  • The framework handles the connection between the two.

🆚 Imperative vs Reactive

Imperative Code Reactive Code
Manually manage state updates Automatically reacts to state changes
Explicit logic flow Declarative data flow
More prone to bugs in UI state Cleaner, more maintainable

✅ Example in Angular

🟦 Imperative Approach:

this.counter = 0;

increment() {
  this.counter++;
  this.updateUI();
}

You manually change the value and then trigger UI updates.


🟩 Reactive with Signal:

counter = signal(0);

// Computed value that reacts to counter changes
double = computed(() => this.counter() * 2);

// This runs automatically when counter or double changes
effect(() => {
  console.log('Double is now:', this.double());
});

// When counter is updated, everything reacts
this.counter.set(this.counter() + 1);

No manual update or tracking — everything is reactive and automatic.


✅ In Angular Context:

Reactive Features:

  • RxJS Observables: Data streams that react to events or HTTP changes
  • Reactive Forms: Automatically track form state and validation
  • Signals: New Angular reactive primitives for zone-less state tracking
  • Change Detection: Reacts to value changes to update the DOM

🎯 Benefits of Reactive Code

  • ✅ Less boilerplate
  • ✅ Better performance (especially with OnPush or Signals)
  • ✅ Easier to manage complex UI state
  • ✅ Makes async operations cleaner (e.g., HTTP requests, timers)

Let's explore reactivity in Angular through three practical and side-by-side examples using:

  1. RxJS Observables
  2. Signals (Angular 16+)
  3. Reactive Forms

Each showcases how Angular handles state reactively — meaning the UI responds automatically to data changes without manual DOM updates.


✅ 1. RxJS Observables (Reactive via Streams)

Use Case: React to timer or HTTP data

@Component({
  selector: 'app-rxjs-demo',
  template: `Time: {{ time }}`
})
export class RxjsDemoComponent implements OnInit {
  time: number = 0;

  ngOnInit() {
    interval(1000).subscribe(value => {
      this.time = value; // UI updates automatically
    });
  }
}
  • interval emits values every second.
  • The subscription reacts to the emission, and Angular reflects it in the DOM.

✅ 2. Signals (Angular 16+): Fine-Grained Reactivity

Use Case: Modern, reactive state without zones

import { signal, computed, effect } from '@angular/core';

@Component({
  selector: 'app-signal-demo',
  standalone: true,
  template: `
    Count: {{ count() }}
    Double: {{ double() }}
    Increment
  `
})
export class SignalDemoComponent {
  count = signal(0);
  double = computed(() => this.count() * 2);

  constructor() {
    effect(() => {
      console.log('Double changed to', this.double());
    });
  }

  increment() {
    this.count.set(this.count() + 1);
  }
}
  • Reactive signals track dependencies and automatically update.
  • effect() reacts to any change without manually subscribing.
  • Cleaner, faster, and no zone.js needed.

✅ 3. Reactive Forms: Reactive Form Validation and Updates

Use Case: Live form validation and value tracking

@Component({
  selector: 'app-form-demo',
  template: `
    
      
    
    Name Length: {{ nameLength }}
  `
})
export class FormDemoComponent implements OnInit {
  form: FormGroup;
  nameLength: number = 0;

  constructor(private fb: FormBuilder) {
    this.form = this.fb.group({
      name: ['']
    });
  }

  ngOnInit() {
    this.form.get('name')!.valueChanges.subscribe(value => {
      this.nameLength = value.length;
    });
  }
}
  • The form control emits changes reactively.
  • We subscribe to those changes and update the UI live.

🧠 Summary Table

Feature RxJS Observable Signal Reactive Forms
Introduced In Angular 2+ Angular 16+ Angular 2+
Async Stream Yes No (sync, state-based) No
Use Case HTTP, events Local state mgmt Forms
Subscriptions Manual (subscribe) Automatic via effect Manual (valueChanges)
Zone.js needed? Yes (by default) No Yes

Now, Let’s now build a real-time mini-app using Signals with a Signal Store, showcasing parent-child communication without @Output, using a shared reactive state.


✅ Scenario: Live User Profile Editor with Signals

  • 💡 Signal Store holds the shared state (username).
  • 📦 Parent Component displays user info.
  • ✏️ Child Component allows editing the username.
  • 🔄 No EventEmitter, no manual change detection — just pure reactivity.

🏗️ Step-by-Step Setup


1️⃣ Create a Shared Signal Store Service

import { Injectable, signal } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class UserStore {
  username = signal('Alice');

  updateUsername(newName: string) {
    this.username.set(newName);
  }
}
  • username is a signal: a reactive state container.
  • Any component that reads this will automatically react to changes.

2️⃣ Parent Component: UserProfileComponent

import { Component, computed, inject } from '@angular/core';
import { UserStore } from './user.store';
import { UserEditorComponent } from './user-editor.component';

@Component({
  selector: 'app-user-profile',
  standalone: true,
  imports: [UserEditorComponent],
  template: `
    User Profile
    Welcome, {{ username() }}!
    
  `
})
export class UserProfileComponent {
  private store = inject(UserStore);
  username = computed(() => this.store.username());
}
  • Uses computed() for reactive consumption of the username signal.
  • Re-renders automatically when the signal updates — no subscriptions needed.

3️⃣ Child Component: UserEditorComponent

import { Component, inject } from '@angular/core';
import { UserStore } from './user.store';

@Component({
  selector: 'app-user-editor',
  standalone: true,
  template: `
    
  `
})
export class UserEditorComponent {
  store = inject(UserStore);
}
  • Reads the signal directly from the store.
  • Updates it with updateUsername, which automatically triggers updates in all other components using the same signal.

✅ Live Flow Example:

  1. User visits dashboard: sees "Welcome, Alice".
  2. Edits name in child component input to "Bob".
  3. Signal store updates → parent auto-reacts → shows "Welcome, Bob".

🎁 Bonus: Add a Derived Value with computed()

In the parent, add:

usernameUpper = computed(() => this.store.username().toUpperCase());

Template:

Username in caps: {{ usernameUpper() }}

✔️ This will always be up-to-date without manual updates or watchers.


🔄 Summary

Element Role Reactivity
signal() Holds mutable state
computed() Derived, auto-updating
effect() Side-effects on change
Shared Service App-level state sharing

In Angular, both constructor and inject() can be used to get dependencies, but they serve different purposes and are used in different contexts.

Let’s break it down:


🔍 1. Constructor-based Dependency Injection

✅ Traditional and most common method.

@Component({ ... })
export class MyComponent {
  constructor(private userService: UserService) {}

  ngOnInit() {
    this.userService.loadUser();
  }
}

🔧 How it works:

  • Angular uses constructor injection to create instances.
  • Dependencies are injected when the class is instantiated.
  • Works in all Angular classes: components, services, pipes, etc.

✅ Benefits:

  • Easy to read, widely used, works everywhere.
  • Keeps dependencies explicit.

❌ Limitations:

  • Cannot be used outside class context (e.g., inside functions or computed/effect outside class methods).

🔥 2. inject() Function (from Angular v14+)

import { inject } from '@angular/core';

@Component({ ... })
export class MyComponent {
  userService = inject(UserService);

  ngOnInit() {
    this.userService.loadUser();
  }
}

🔧 How it works:

  • inject() can be used at the top level of a class.
  • You don’t need to use the constructor to inject a service.
  • It’s a function — not part of the DI constructor mechanism.

✅ Benefits:

  • Works even outside class constructors, such as:
    • Signals
    • computed() or effect() scopes
    • Standalone functions or utilities

❌ Limitations:

  • Can only be used within Angular-managed code (like components, services, etc.).
  • Harder to mock in unit tests without DI frameworks or tools like TestBed.overrideProvider.

✅ When to Use Which?

Use Case Use constructor? Use inject()?
Regular services in components ✅ Yes ✅ Yes
Inject inside computed()/effect() ❌ No ✅ Yes
Want concise class with less boilerplate ❌ Optional ✅ Clean syntax
Want to test easily ✅ Yes ❌ Harder to override
Angular Standalone APIs ✅/❌ ✅ Especially for Signals

✅ Real World Mixed Example

export class MyComponent {
  private logger = inject(LoggerService);  // used in a signal or effect
  username = signal('');

  constructor(private userService: UserService) {}

  ngOnInit() {
    this.logger.log('Component loaded');
    this.userService.loadUser();
  }
}

🧠 TL;DR

Feature constructor inject()
Introduced Angular 2 Angular 14+
Type OOP-style (class-based) Functional-style
Readability ✅ Very clear ✅ Clean but less explicit
Flexibility ❌ Constructor only ✅ Use anywhere (signals, etc)
Testability ✅ Easy to mock ❌ Needs override setup