There are various ways of passing data between components in Angular. In this article, we will be looking at component interaction using Shared Services and RxJS Subjects.
In case if you don’t know about Services, Service is a class in Angular which is decorated with @injectable
decorator. It serves two main purposes:
- To pass data between different components.
- To separate certain processing tasks such as network calls and their error handling, logging directly to the console etc from component.
What is an RxJS Subject??
A Subject is a special kind of Observable from the RxJS library which allows us to multicast values to the components which have subscribed to it. Whenever Subject emits a value, each of its subscribers gets notified about the emitted value.
It has three methods:
next(value)
- This method is used to emit a new value.error(error)
- This method is used to send error notifications.complete()
- This method suggests that the Subject has completed itswork. Oncecomplete()
method is invoked, callingnext()
orerror()
won’t have any effect.
We will create a small application where:
- The user will create a post (create-post component).
- Upon submission, post will be displayed in posts component and count will be incremented and reflected in posts-count component.
Let's create a new application using the below command.
ng new my-app
We will have three components.
- create-post — This component will contain a form where the user will enter the details.
- posts — This component will display all the posts
- posts-count — This component will display total posts count.
We will also create a Shared Service.
Use the below commands to create the components and service using Angular-CLI
ng generate component create-post
ng generate component posts
ng generate component posts-countng generate service shared
Shared Service
- Import
Subject
from‘rxjs’
. - Create a
posts
Subject usingnew Subject()
. - Write a method
setPosts
for emitting the value usingnext()
method.
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';@Injectable({
providedIn: 'root'
})export class SharedService {
public posts = new Subject(); constructor() { } setPost(post) {
this.posts.next(post);
} getPosts() {
return this.posts;
}
}
create-post component
This component has a reactive form for capturing the user input. Upon submission, we are calling the service’s setPost
method to emit the post.
TS
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
import { SharedService } from '../services/shared.service';@Component({
selector: 'app-create-post',
templateUrl: './create-post.component.html',
styleUrls: ['./create-post.component.css']
})export class CreatePostComponent implements OnInit {
public postForm: FormGroup; constructor(private sharedService: SharedService) { } ngOnInit(): void {
this.postForm = new FormGroup({
name: new FormControl(''),
post: new FormControl('')
})
} submit() {
const post = {
name: this.postForm.get('name').value,
post: this.postForm.get('post').value
}
this.sharedService.setPost(post);
}
}
HTML
<div class = "card">
<div class = "card-header">Create Post</div>
<div class = "card-body">
<form [formGroup] = "postForm">
<div class="form-group">
<label>Name</label>
<input type="email"
class="form-control"
formControlName = "name">
</div>
<div class="form-group">
<label>Post</label>
<textarea class="form-control"
rows="3"
formControlName = "post">
</textarea>
</div>
<div class = "text-center">
<button class = "btn btn-primary"
(click) = "submit()">Submit
</button>
</div>
</form>
</div>
</div>
posts-component
In this component, we will call getPosts
method of the Shared Service. Since getPosts
method is returning a Subject, we will subscribe to it for setting up the subscription.
Every time posts
Subject emits the post, we will push the post value to posts
array.
In ngOnDestroy()
lifecycle hook, we are unsubscribing from the subscription. If we don’t unsubscribe before the component gets destroyed, it can cause memory leaks.
TS
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subscription } from 'rxjs';
import { SharedService } from '../services/shared.service';@Component({
selector: 'app-posts',
templateUrl: './posts.component.html',
styleUrls: ['./posts.component.css']
})export class PostsComponent implements OnInit, OnDestroy {
public posts = [];
public postsSubscription: Subscription; constructor(private shareService: SharedService) { } ngOnInit(): void {
this.postsSubscription = this.shareService.getPosts()
.subscribe(response => {
this.posts.push(response);
})
} ngOnDestroy() {
this.postsSubscription.unsubscribe();
}
}
HTML
Using ngFor
directive, we will render the posts.
<ng-container *ngIf = "posts.length>0">
<h5 class = "mt-3 text-center">Posts</h5>
<div class = "col-sm-8 mt-1 p-1 mx-auto text-white bg-secondary"
*ngFor = "let post of posts">
<p>{{post.name}}</p>
<p>{{post.post}}</p>
</div>
</ng-container>
posts-count component
This is same as the posts component. With each emitted value, we are incrementing the posts
counter.
TS
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subscription } from 'rxjs';
import { SharedService } from '../services/shared.service';@Component({
selector: 'app-posts-count',
templateUrl: './posts-count.component.html',
styleUrls: ['./posts-count.component.css']
})export class PostsCountComponent implements OnInit, OnDestroy {
public postsCount = 0;
public postsSubscription: Subscription;
constructor(private sharedService: SharedService) { }
ngOnInit(): void {
this.postsSubscription = this.sharedService.getPosts()
.subscribe(response => {
this.postsCount++;
})
} ngOnDestroy() {
this.postsSubscription.unsubscribe();
}
}
HTML
<div class = "box text-center text-white">
Total Posts: {{postsCount}}
</div>
Remember with normal Subject, subscribers will only get the values which are emitted after they have subscribed. If you want to get last emitted values, you can use BehaviorSubject
or ReplaySubject
.
Subject has three types. Lets look at them one by one.
BehaviorSubjectBehaviorSubject
stores the last emitted value. When a new user subscribes, he will immediately get the stored value. While creating BehaviorSubject, we have to provide initial value.
Syntax:
import { BehaviorSubject } from 'rxjs';const subject = new BehaviorSubject(inital_value);
ReplaySubjectReplaySubject
can emit multiple old values to new subscribers. While creating the ReplaySubject, you have to specify, how many old values you want to store and for how long you want to keep them.
Syntax:
import { ReplaySubject } from 'rxjs';const subject = new ReplaySubject(No_of_values, Time);
AsyncSubjectAsyncSubject
is different when compared to BehaviorSubject
and ReplaySubject
. It only emits the last value and that last value is emitted when its execution gets completed.
Syntax:
import { AsyncSubject } from 'rxjs';const subject = new AsyncSubject();
Let’s Summarize !!
In a nutshell, Subjects are useful for multicasting values to different components in Angular. Whenever the Subject emits a value, components get notified. BehaviorSubject
, ReplaySubject
and AsyncSubject
are three variants of Subject.
next(value)
, error(error)
and complete()
are three methods associated with Subject which are used to emit a value, send error notification and execution completion notification respectively.
Hope the above information helps you in getting a clear picture of Subjects !!