Angular: Creating Dynamically Updating Chart with ng2-charts and chart.js with capacilities of Rxjs
I wrote an article on creating Real-Time Charts using SignalR and Blazor. After reading this article, some of my students were looking for the same chart in Angular, so I decided to write on this topic. In Angular, we can use JavaScript timer functions, e.g., setInterval(), to call the server side to fetch data and process it on the client side. But instead of using such a JavaScript function, we should use RxJs to handle time-based or time-bound operations.
What is RxJs?
RxJS, or Reactive Extensions for JavaScript, is a library for composing asynchronous and event-based programs using observable sequences. RxJs provides a powerful and flexible way to handle data streams and asynchronous events that makes it easier and smarter to manage complex asynchronous tasks in JavaScript applications.
Following are some of the most important key features of RxJs:
Observables: They represent data streams. These data steams can be observed and manipulated. Observables can emit data items over time, which can be observed and reacted to.
Operators: There are a variety of operators in RxJs that are used to transform, filter, and combine data streams. These operators make it easy to perform complex data manipulations with simple, composable functions.
Subscriptions: A subscription in RxJs is an execution of an observable. When the application subscribes to an observable, it starts receiving data items emitted by the observable. Subscriptions can also be canceled to stop receiving data. The Subscription class represents a disposable resource, such as the execution of an Observable. A Subscription has one important method, unsubscribe, that takes no argument and just disposes of the resource held by the subscription.
Additionally, subscriptions may be grouped together through the add() method, which will be used to attach a child Subscription to the current Subscription. When a Subscription is unsubscribed, all its children and its grandchildren will be unsubscribed as well.
Subjects: Subjects in RxJs are special types of observables that can act as both an observable and an observer. They allow multicasting of data to multiple subscribers.
Schedulers: Schedulers control the execution context of observables, allowing the application to manage concurrency and control the timing of data emissions.
Interval: This is a function that creates an Observable that emits sequential numbers every specified interval of time on a specified SchedulerLike. This Emits incremental numbers periodically in time.
The above points provide an overview explanation of RxJs. Now, let's start developing the application. I have created an ASP.NET Core 9 API that emits the data from the server. This data is of the company stock volumn. This API sends the stock volume based on the HTTP request. The code for the API can be downloaded from this link.
Modification in ng2-charts for Angular 15+
In the latest Angular versions (precisely from version 15+), we have standalone components. Angular does not use the Modular approach. (Although we can still do it using ng new [PROJECT-NAME] --standalone=false command), We need to bootstrap components as well as configure the HTTP client and other application-level configurations in the app.config.ts file. While using ng2-charts in Angular standalone apps, we need to focus on changes in ng2-charts. These changes are as follows:
provideCharts:
The provideCharts
is a function in ng2-charts. It
It is used to configure and provide the necessary components for the Angular application. It allows registering the required components in chart.js and making them available throughout your application.
withDefaultRegisterables()
The withDefaultRegisterables() function in ng2-charts is used to register all the default Chart.js components (such as chart types, elements, scales, and plugins) when configuring the provideCharts function. This ensures that the necessary Chart.js components are available for use throughout the Angular application without having to manually register each one.
Implementing the Angular Application
Step 1: Open the Command prompt (Or Terminal Window if using Linux or Mac) and create an Angular application using the following command
ng new ng-chart
Once the project is created, navigate to the ng-chart folder and install packages like ng2-charts, chart.js, and bootstrap using the following command.
npm install ng2-charts chart.js bootstrap
Step 2: In the project, add a new folder named models. In this folder, add a new file named app.market.model.ts. In this file, we will create a MarketData enter class. This class maps with the entity class in the ASP.NET Core API. Listing 1 shows the code for this class.
export class MarketData { public CompanyName:string=''; public Volume:number = 0; }
Listing 1: The MarketData class
Step 3: In the project, add a new folder named services. In this folder, add a new file named marketdta.service.ts. In this file, we will create an Angular service that will make an HTTP request to the ASP.NET Core API. Listing 2 shows the code for the Angular service.
import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { MarketData } from '../models/app.market.model'; @Injectable({ providedIn: 'root' }) export class MarketdataService { private url:string = 'http://localhost:5014/marketdata' constructor(private http:HttpClient) { } getdata(): Observable<MarketData[]> { return this.http.get<MarketData[]>(this.url); } }
Listing 2: The Angular service code
The service contains the getdata() method that returns an Observable of the MarketData Array. This data will be used to generate the chart.
Step 4: Once the service is ready, we will modify the code in app.config.ts to register the HTTP Client as well as chart components from chart.js, which are used by ng2-charts. Listing 3 shows the code for registration.
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; import { provideRouter } from '@angular/router'; import { routes } from './app.routes'; import { provideHttpClient } from '@angular/common/http'; import { provideCharts, withDefaultRegisterables } from 'ng2-charts' export const appConfig: ApplicationConfig = { providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideHttpClient(),provideCharts(withDefaultRegisterables())] };
Listing 3: The HTTP Client and the chart component registration
Step 5: Replace the code from the app.component.ts file to access the MarketdataService created in step 3. In this component, we will use the interval() function from RxJs that will invoke the getdata() method from the MarketdataService after each 1000 milliseconds. Further, it will subscribe to the observable returned from it and then the received data will be processed so that it can be used to draw chart. Listing 4 shows the code for the AppComponent.
import { Component, OnInit, ViewChild,ChangeDetectorRef,OnDestroy } from '@angular/core'; import { switchMap} from 'rxjs/operators'; import { interval, Subscription } from 'rxjs' import { BaseChartDirective } from 'ng2-charts'; import { MarketdataService } from './services/marketdata.service'; @Component({ selector: 'app-root', standalone: true, imports: [BaseChartDirective], templateUrl: './app.component.html', styleUrl: './app.component.css' }) export class AppComponent implements OnInit, OnDestroy { marketDataRecords:any[]; labels:Array<string>; chartOptions = { responsive: true }; chartType = 'line'; private subscription!: Subscription constructor(private serv:MarketdataService, private cdr: ChangeDetectorRef){ this.marketDataRecords = []; this.labels = new Array<string>(); } ngOnDestroy(): void { if (this.subscription) { this.subscription.unsubscribe(); } } ngOnInit(): void { this.subscription = interval(1000) .pipe(switchMap(() => this.serv.getdata())) .subscribe( (response) => { this.marketDataRecords = [{ data: response.map((item)=>item.Volume), label: 'Market Data', borderColor:'red', borderWidth:2, fill:false }]; this.labels = response.map((item)=>item.CompanyName); this.cdr.detectChanges(); // Trigger change detection } ); } }
Listing 4: The AppComponent
As shown in Listign 4, the response received from the HTTP class is processed so that the chart can be drawn. The marketDataRecords array is used to show data on the Y-Axis to draw a chart line based on the Volume. The chart labels are read from the response as ComanyArray so that they can be shown on the X-axis. The Data change for the chart must be updated to the UI. To do this, the component is injected with the ChangeDetectorRef. This is a powerful service that provides mechanisms for manually controlling change detection in your application. It is primarily used to optimize performance and manage complex scenarios where Angular's automatic change detection may not be sufficient.
Step 6: Replace the code for the app.component.html to show the chart as shown in Listing 5.
<h4>The Updating chart</h4> <div class="container"> <canvas baseChart [datasets]="marketDataRecords" [labels]="labels" [options]="chartOptions" [type]="'line'" > </canvas> </div>
Listing 5: The UI
As shown in Listing 5, the markup uses the canvas with the baseChart directive. The baseChart
directive in ng2-charts
is used to create Chart.js charts in Angular applications. It provides an Angular-friendly interface to configure and manage charts. Furthermore, the dataSource property is bound with the marketDataRecords array that has the data to draw the chart. The labels property is bound to the labels property of the component that shows the company names on the X-axis. The chartOptions property is used to make the chart as responsive as defined in the component code. The type property creates the line chart.
Run the ASP.NET Core API application and the Angular application. The resulting chart will keep changing after each 1 second as shown in the following video.
The code for this article can be downloaded from this link.
Conclusion: Angular with RxJs capabilities and chart.js with ng2-charts provide an easy mechanism to draw dynamic charts.