Angular Observable Subject Example Sharing Data Between Components

In this tutorial, we will show you how to use Subjects in Angular with examples. We learned What is Subjects in Angular and different types of subjects like ReplaySubject, BehaviorSubject & AsyncSubject in Angular

Angular Subject Example

We will build a todo app. Our app has two components.

One is the Todo list component, which displays the list of todos. It also has the option to delete a todo

Another one is Todo add a component, which allows us to add a Todo item.

Both will communicate with each other via Service. Whenever a new todo is added, the service will notify the Todo list component to update the lists using a observable

Code is available at StackBlitz

Todo Service Using BehaviorSubject

Create the todo.service.ts in the src\app folder.

todo.service.ts

import { Injectable } from "@angular/core";
import { BehaviorSubject } from "rxjs";

export interface Todo {
  id: any;
  value: string;
}

@Injectable()
export class TodoService {

  private _todo = new BehaviorSubject<Todo[]>([]);
  readonly todos$ = this._todo.asObservable();

  private todos: Todo[] = [];
  private nextId = 0;

  constructor() {}

  loadAll() {
    this.todos = [];
    this._todo.next(this.todos);
  }

  create(item: Todo) {
    item.id = ++this.nextId;
    this.todos.push(item);
    this._todo.next(Object.assign([], this.todos));
  }

  remove(id: number) {
    this.todos.forEach((t, i) => {
      if (t.id === id) {
        this.todos.splice(i, 1);
      }
      this._todo.next(Object.assign([], this.todos));
    });
  }
}

Here, we create BehaviorSubject of type Todo[]. Behavior expects us to provide an initial value. We assign an empty array. The BehaviorSubject will always emit the latest list of Todo items as an array. We can also use Subject here. But the advantage of BehaviorSubject is that the late subscribers will always receive the latest list of Todo items immediately on the subscription. We do not have to call the next method.

  private _todo$ = new BehaviorSubject<Todo[]>([]);

Also, it is advisable not to expose the BehaviorSubject outside the service. Hence we convert it to normal Observable and return it. This is because the methods like next, complete or error do not exist on normal observable. It will ensure that the end-user will not accidentally call them and mess up with it

  readonly todos$ = this._todo.asObservable();

The todos will store the todo items in memory. We use the nextId to create the id of the Todo item.

  private todos: Todo[] = [];
  private nextId = 0;

Create pushes the new item to the todos list. Here we use the next method to push the todos list to all the subscribers. Note that we use the Object.assign to create a new copy of the todos and push it to the subscribers. This will protect our original copy of the todos list from accidental modification by the user.

  create(item: Todo) {
    item.id = ++this.nextId;

    //Update database
    this.todos.push(item);
    this._todo$.next(Object.assign([], this.todos));
  }

Remove method removes the Todo item based on id and pushes the new list to subscribers.

  remove(id: number) {
    this.todos.forEach((t, i) => {
      if (t.id === id) {
        this.todos.splice(i, 1);
      }
      this._todo$.next(Object.assign([], this.todos));
    });
  }

TodoList Component

TodoListComponent displays the list of Todo items.

todo-list.component.ts

import { Component, OnInit } from "@angular/core";
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
import { Observable } from "rxjs";
import { map } from "rxjs/operators";

import { Todo, TodoService } from "./todo.service";

@Component({
  selector: "app-todo",
  template: 
    <div *ngFor="let todo of (todos$ | async)">
      {{ todo.id }} {{ todo.value }}
      <button (click)="deleteTodo(todo.id)">x</button>
    </div>
  
})
export class TodoListComponent implements OnInit {
  todos$: Observable<Todo[]>;

  constructor(private todoService: TodoService) {}

  ngOnInit() {
    this.todos$ = this.todoService.todos$;
  }

  deleteTodo(todoId: number) {
    this.todoService.remove(todoId);
  }
}

We inject todoService

 constructor(private todoService: TodoService) {}

And get hold of a reference to the todo$ observable.

this.todos$ = this.todoService.todos$;

We use the async pipes to subscribe to the todos$ observable. No need to worry about unsubscribing the observable as angular handles it when using async pipes

    <div *ngFor="let todo of (todos$ | async)">
      {{ todo.id }} {{ todo.value }}
      <button (click)="deleteTodo(todo.id)">x</button>
    </div>

deleteTodo deletes the Todo item by calling the remove method of the todoService

  deleteTodo(todoId: number) {
    this.todoService.remove(todoId);
  }

TodoAdd Component

We use TodoAddComponent to add a new Todo item

todo-add.component.ts

import { Component, OnInit } from "@angular/core";
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
import { Observable } from "rxjs";
import { map } from "rxjs/operators";

import { Todo, TodoService } from "./todo.service";

@Component({
  selector: "app-todo-add",
  template: 
    <div>
      <form [formGroup]="todoForm" (submit)="onSubmit()">
        <p>
          <input type="text" id="value" name="value" formControlName="value" />
        </p>

        <button type="submit">Add Item</button><br />
      </form>
    </div>
  
})
export class TodoAddComponent implements OnInit {
  todos$: Observable<Todo[]>;
  todoForm: FormGroup;

  constructor(
    private todoService: TodoService,
    private formBuilder: FormBuilder
  ) {
    this.todoForm = this.formBuilder.group({
      id: [""],
      value: ["", Validators.required]
    });
  }

  ngOnInit() {
    this.todos$ = this.todoService.todos$;
  }

  onSubmit() {
    this.todoService.create(this.todoForm.value);
    this.todoForm.get("value").setValue("");
  }
}

First inject todoService

  constructor(
    private todoService: TodoService,
    private formBuilder: FormBuilder
  )

We get hold of the todos$ observable. We are not doing anything with it But you can subscribe to it to get the latest list of Todo items.

  ngOnInit() {
    this.todos$ = this.todoService.todos$;
  }

onSubmit method creates a new Todo item by calling the create method of the todoService.

  onSubmit() {
    this.todoService.create(this.todoForm.value);
    this.todoForm.get("value").setValue("");
  }

app.component.html

<app-todo-add></app-todo-add>
<app-todo></app-todo>

References

  1. https://stackblitz.com/edit/angular-fh1kyp
  2. Angular Observable Data Services

1 thought on “Angular Observable Subject Example Sharing Data Between Components”

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Scroll to Top