The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of state changes. This pattern is similar (but not identical) to the publish/subscribe design pattern.
Angular uses Observables to handle asynchronous data streams. It supports passing messages between publishers(Creators of Observables) and subscribers(Users of Observables) in our application.
Observables are declarative; you define the function for publishing values, but it is not executed until the consumer subscribes.
Depending on the context, the observable can deliver multiple values, like literals, messages, or events. For example, as a publisher, you can create an Observable instance that defines a subscriber function. This function is executed when the consumer calls the subscribe() method.
Defining Observers
The handler for receiving the observable notifications implements the Observer interface. It is an object that defines the callback methods to handle the three types of notifications that an observable can send. These are the following.
- next: Required. The handler for each delivered value is called zero or more times after execution starts.
- error: Optional. The handler for error notification. The error halts the execution of the observable instance.
- complete: Optional. The handler for an execution-complete notification. The delayed values can continue to be delivered to the next handler after execution.
Creating Observables
Use the Observables constructor to create an observable stream of any type.
const obs = new Observable();
Subscribing Observables
An important note is that the Observables instance only publishes the values when someone subscribes. You can subscribe to the observables by calling the subscribe() method of the instance and passing an observer object to receive the notifications.
myObservable.subscribe( x => console.log('Observer got a next value: ' + x), err => console.error('Observer got an error: ' + err), () => console.log('Observer got a complete notification') );
Here is a step-by-step guide to implementing Observables in Angular.
Step 1: Setting up a new Project
Install Angular CLI if not installed using the below command:
npm install -g @angular/cli
Now, you can create a new project using the below command:
ng new observable-app
After creating a project, go inside the project:
cd observable-app
Step 2: Generating a Service
We will create a service to create an HTTP GET request to fetch the data from the API.
To create a service in Angular, use the below command:
ng generate service data
It will create two files inside the src/app folder:
- data.service.ts
- data.service.spec.ts
We will write a code to fetch the data from an API in the data.service.ts file:
To send an HTTP request from an application, we will import the HttpClient module.
Step 3: Creating an Observable
Also, we will import the Observable from the ‘rxjs’ library:
Open the src/app/data.service.ts file and add the below code:
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; @Injectable({ providedIn: 'root', }) export class DataService { private apiUrl = 'https://jsonplaceholder.typicode.com/posts'; constructor(private http: HttpClient) {} getData(): Observable<any[]> { return this.http.get<any[]>(this.apiUrl); } }
We defined a private variable apiURL and getData() function in this code.
Angular’s DI framework injects an instance of HttpClient into the DataService when it is created. The injected HttpClient instance makes a GET request to the specified API URL.
The getData() function returns an Observable. You can subscribe to this observable to get the data streams.
Step 4: Implementing the Component
We created a service file and now need to use that service file in our app component.
Modify the src/app/app.component.ts file and replace it with the below code:
import { Component } from '@angular/core'; import { RouterOutlet } from '@angular/router'; import { HttpClientModule } from '@angular/common/http'; import { CommonModule } from '@angular/common'; import { DataService } from './data.service'; @Component({ selector: 'app-root', standalone: true, imports: [RouterOutlet, HttpClientModule, CommonModule], templateUrl: './app.component.html', styleUrl: './app.component.css', providers: [DataService], }) export class AppComponent { posts: any[] = []; constructor(private dataService: DataService) {} ngOnInit(): void { this.dataService.getData().subscribe({ next: (data) => { this.posts = data; }, error: (error) => { console.error('Error fetching data:', error); }, complete: () => { console.log('Data fetching completed'); }, }); } }
In this app.component.ts file, we imported the required modules and files.
We also imported DataService, a custom service defined to fetch data from an API.
The @providers declare an array of services, and we included DataService.
The ngOnInit is a Lifecycle hook that Angular calls after creating the component. Inside the ngOnInit() method, the component subscribes to the getData() method of DataService to fetch data.
Since the getData() method returns an observable, we can call the subscribe() method.
The subscribe() method accepts an object with three optional properties: next, error, and complete:
- next: When the HTTP request successfully returns data, the next callback is executed with the fetched data.
- error: If the HTTP request fails (e.g., network error, server error), an error callback is executed with the error object.
- complete: Once the HTTP request is complete and no more data is sent, the complete callback is executed.
By using subscribe(), the AppComponent can handle asynchronous data operations, ensuring that data is fetched, errors are managed, and completion is logged appropriately.
So, when we subscribe to the data stream from an API, we will use the next() callback method, which returns the data and stores it in the local posts array variable.
Now, we need to display the posts.
Step 5: Writing an HTML to display the data
Open the src/app/app.component.html file and add the below code:
<div *ngFor="let post of posts" class="post"> <h2>{{ post.title }}</h2> <p>{{ post.body }}</p> <small>User ID: {{ post.userId }} | Post ID: {{ post.id }}</small> </div>
Also, add the CSS for styling inside the src/app/app.component.css file:
.post { border: 1px solid #ddd; padding: 15px; margin-bottom: 15px; border-radius: 5px; } .post h2 { margin-top: 0; } .post p { margin: 5px 0; } .post small { color: #555; }
Step 6: Running the application
Run your project using the ng serve –open command:
You can see that we successfully fetched the API Data and displayed it in a proper format.
Step 7: Unsubscribing the Observables
Managing unsubscriptions in Angular is essential to prevent memory leaks and make the application more efficient.
The most efficient way to unsubscribe all the observables is inside the ngOnDestroy() hook using the unsubscribe() method.
Angular has a ngOnDestroy() hook that is called before a component is destroyed. This enables developers to provide the cleanup to avoid hanging subscriptions.
Here is how you can implement logic to unsubscribe from observables.
Add the following code inside the app.component.ts file:
import { Component } from '@angular/core'; import { RouterOutlet } from '@angular/router'; import { HttpClientModule } from '@angular/common/http'; import { CommonModule } from '@angular/common'; import { DataService } from './data.service'; import { Subscription } from 'rxjs'; @Component({ selector: 'app-root', standalone: true, imports: [RouterOutlet, HttpClientModule, CommonModule], templateUrl: './app.component.html', styleUrl: './app.component.css', providers: [DataService], }) export class AppComponent { public posts: any[] = []; private subscription1$!: Subscription; constructor(private dataService: DataService) {} ngOnInit(): void { this.subscription1$ = this.dataService.getData().subscribe({ next: (data) => { this.posts = data; }, error: (error) => { console.error('Error fetching data:', error); }, complete: () => { console.log('Data fetching completed'); }, }); } ngOnDestroy() { if (this.subscription1$) { this.subscription1$.unsubscribe(); } } }
Basically, in this code, if a subscription is hanging, we will unsubscribe from it and destroy the component.
That’s all!
Kay
You need to cover observer pattern at a high level, subscriber models, multiple susbcription, caching scenarios, cold v hot, connectable observables, operators etc. In the cases you’ve outlined, manual unsubscription shouldn’t be needed and should be discouraged as correctly written finite observables should handle subscriber unsubscription internally and complete the observable. Hence why you don’t need to unubscribe from HttpClient observables.
Side note: From a purely angular 7 standpoint this entire post defies best practice. Was the angular 7 ref just for the SEO potential?
HEMANG
Isn’t this just Rxjs? What is Angular 7 has to do with Observables?
By reading the title, I thought Observables are now part of Angular version 7.
Krunal
Yes, you are right, both are a totally different thing. The main motto of this example is that how we can use Angular with Observables and yes if you install angular then rxjs will be installed as well. So rxjs is kind of part of angular projects.
Tatiana
It could be helpful if you also provide a sample of unit tests for your class
zafar
it is helpful if you give a high level eg to use observables
zafar
it is helpful if you give a high level eg to use observables and to make in real time
Sunethra Krishnaraj
This is the best easiest example for a beginner like me. To explain this high level, we need to first make sure that we are getting the basics right with the beginners.
I would be happier if you post one more basic working example for Observables, subscribe.
Thank you.
Anjana Choudhary
Hi,
I tried this example with one variable declared in generic utility class. this variable value is updated by different component in application. so my problem is, i want to listen this variable of custom interface type for eg activeFunctionDiagram: FunctionDiagram, so here activeFunctionDiagram is variable of type FunctionDiagram. how can i do this, any solution? one thing i tried is create observable on this variable, but i subscribe it in utility class constructor itself, it give me the first value of variable, not the different value, when other component change this variable value or simply update any one property of this variable.