Angular course Β· Module 7: RxJS

Subjects - Multi-directional Communication

2 min read Β·
In this lesson2

Subjects are special Observables that can both emit and receive values - like a ninja command center.

Types of Subjects

import { Subject, BehaviorSubject, ReplaySubject, AsyncSubject } from 'rxjs';

// Subject - basic, no memory
const subject = new Subject<string>();
subject.subscribe(v => console.log('A:', v));
subject.next('Hello');  // A: Hello
subject.subscribe(v => console.log('B:', v));
subject.next('World');  // A: World, B: World
// B did not receive 'Hello' - there was no subscription

// BehaviorSubject - remembers the last value
const behavior = new BehaviorSubject<number>(0);  // Requires initial value
behavior.subscribe(v => console.log('A:', v));  // A: 0 (immediately!)
behavior.next(1);  // A: 1
behavior.subscribe(v => console.log('B:', v));  // B: 1 (last value!)
behavior.next(2);  // A: 2, B: 2

// ReplaySubject - remembers the last n values
const replay = new ReplaySubject<number>(2);  // Remembers last 2
replay.next(1);
replay.next(2);
replay.next(3);
replay.subscribe(v => console.log(v));  // 2, 3 (last 2)

// AsyncSubject - emits only the last value on complete
const async = new AsyncSubject<number>();
async.next(1);
async.next(2);
async.subscribe(v => console.log(v));  // Nothing
async.next(3);
async.complete();  // Now: 3

Practical Example - State Service

@Injectable({ providedIn: 'root' })
export class NotificationService {
  private notificationsSubject = new BehaviorSubject<Notification[]>([]);

  // We expose only the Observable (readonly)
  readonly notifications$ = this.notificationsSubject.asObservable();

  // Current value available synchronously
  get currentNotifications(): Notification[] {
    return this.notificationsSubject.getValue();
  }

  addNotification(notification: Notification): void {
    const current = this.currentNotifications;
    this.notificationsSubject.next([...current, notification]);
  }

  removeNotification(id: number): void {
    const filtered = this.currentNotifications.filter(n => n.id !== id);
    this.notificationsSubject.next(filtered);
  }

  clearAll(): void {
    this.notificationsSubject.next([]);
  }
}

// Usage in a component
@Component({
  template: `
    @for (notification of notifications$ | async; track notification.id) {
      <div class="notification" (click)="remove(notification.id)">
        {{ notification.message }}
      </div>
    }
  `
})
export class NotificationListComponent {
  notifications$ = inject(NotificationService).notifications$;
  private notificationService = inject(NotificationService);

  remove(id: number): void {
    this.notificationService.removeNotification(id);
  }
}

Docs entries used in this lesson

Spotted a mistake in this lesson?

Check yourself

Answer the questions from this lesson. Pick an answer to see right away whether it is correct.

  1. 1. How does a BehaviorSubject differ from a regular Subject?

  2. 2. What characterizes a ReplaySubject?

These are 2 of 4 questions for this lesson. Solve the rest in the game.

Hands-on tasks in the game

  • Code editor

    Complete the user state service: it needs a Subject with an initial value that remembers the last value and returns it with getValue().

  • Horizontal ordering

    Arrange the creation of a BehaviorSubject with an initial value of 0:

  • Vertical ordering

    Arrange the lines of the method that adds a notification to a service based on a BehaviorSubject:

Useful articles