In modern Angular applications, seamless communication between components is essential. When dealing with loosely coupled components that aren't directly related (e.g., parent-child), RxJS Subjects offers an elegant solution for real-time data sharing. This article walks through a real-world use case: implementing a notification system where one component sends notifications and another receives them in real-time. We'll explore the best practices, optimizations, and pitfalls to avoid when working with Subjects.

Angular applications

What is a Subject?

A Subject is a special kind of Observable in RxJS that acts as both a data producer and consumer. Unlike regular observables, Subjects allow multicasting, meaning multiple subscribers receive the same data stream simultaneously. This is particularly useful for,

How Does it Work?

A Subject maintains an internal state and pushes new data to all its subscribers whenever .next(value) is called. Any component subscribing to it immediately receives the latest emitted value.

Application Overview

We will build a simple notification system with two components.

Features

Create the Notification Service

We start by creating a service to manage and distribute notifications using an RxJS Subject.

// notification.service.ts
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class NotificationService {
  private notificationSubject = new Subject<string>();
  notifications$ = this.notificationSubject.asObservable();

  // Function to send a notification
  sendNotification(message: string) {
    this.notificationSubject.next(message);
  }
}

Build the Sender Component

This component provides an input field where users can enter messages and send notifications.

// sender.component.ts
import { Component } from '@angular/core';
import { NotificationService } from './notification.service';

@Component({
  selector: 'app-sender',
  template: `
    <input [(ngModel)]="message" placeholder="Enter notification">
    <button (click)="send()">Send</button>
  `,
})
export class SenderComponent {
  message = '';

  constructor(private notificationService: NotificationService) {}

  send() {
    if (this.message.trim()) {
      this.notificationService.sendNotification(this.message);
      this.message = '';
    }
  }
}

Build the Receiver Component

This component listens for incoming notifications and displays them in real time.

// receiver.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
import { NotificationService } from './notification.service';

@Component({
  selector: 'app-receiver',
  template: `
    <h3>Notifications</h3>
    <ul>
      <li *ngFor="let notification of notifications">{{ notification }}</li>
    </ul>
  `,
})
export class ReceiverComponent implements OnInit, OnDestroy {
  notifications: string[] = [];
  private subscription!: Subscription;

  constructor(private notificationService: NotificationService) {}

  ngOnInit() {
    this.subscription = this.notificationService.notifications$.subscribe(
      (message) => this.notifications.push(message)
    );
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

Configure the App Module

Import necessary modules and declare components.

// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms'; // Required for ngModel
import { AppComponent } from './app.component';
import { SenderComponent } from './sender.component';
import { ReceiverComponent } from './receiver.component';

@NgModule({
  declarations: [AppComponent, SenderComponent, ReceiverComponent],
  imports: [BrowserModule, FormsModule],
  bootstrap: [AppComponent],
})
export class AppModule {}

Update the App Component

Arrange the sender and receiver components side by side.

// app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <div style="display: flex; gap: 20px; padding: 20px;">
      <app-sender></app-sender>
      <app-receiver></app-receiver>
    </div>
  `,
})
export class AppComponent {}

Best Practices

Unsubscribe to Prevent Memory Leaks

Use ngOnDestroy to clean up subscriptions.

Use the Async Pipe (Alternative)

Avoid manual subscriptions with the async pipe.

<!-- receiver.component.html -->
<li *ngFor="let notification of notifications$ | async">{{ notification }}</li>
// receiver.component.ts (alternative)
notifications$ = this.notificationService.notifications$;

Implement Error Handling

Always handle errors in subscriptions.

By leveraging RxJS Subjects, we’ve built a real-time notification system that allows components to communicate without direct dependencies. This approach is scalable, maintainable, and extensible, making it ideal for modern Angular applications.