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
Table of Contents
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | 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.
1 2 3 4 | 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
1 2 3 | 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.
1 2 3 4 | 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.
1 2 3 4 5 6 7 8 9 | 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.
1 2 3 4 5 6 7 8 9 10 | 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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
1 2 3 | constructor(private todoService: TodoService) {} |
And get hold of a reference to the todo$
observable.
1 2 3 | 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
1 2 3 4 5 6 | <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
1 2 3 4 5 | deleteTodo(todoId: number) { this.todoService.remove(todoId); } |
TodoAdd Component
We use TodoAddComponent
to add a new Todo item
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | 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
1 2 3 4 5 6 | 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.
1 2 3 4 5 | ngOnInit() { this.todos$ = this.todoService.todos$; } |
onSubmit
method creates a new Todo item by calling the create
method of the todoService
.
1 2 3 4 5 6 | onSubmit() { this.todoService.create(this.todoForm.value); this.todoForm.get("value").setValue(""); } |
1 2 3 4 | <app-todo-add></app-todo-add> <app-todo></app-todo> |
this article is very confusing, u use app-todo but no open this comp,