I’m trying to build a dashboard for charts using angular 2 and chart.js (via ngcharts). I’d like to have an array of charts that each update via an http request on a custom interval.
Right now I have three separate chart calls that push data to an array. I’m having trouble when it comes to the next iteration - if I push to the array again, I’ll end up with 3 more charts. I’d like the subscribers in the array to update with new data when the interval emits it.
I’m a little confused as to how to correctly structure the component/service/http relationship for my use case. I feel that I’m close but I’m definitely missing something. How can I get the interval/subscriber relationship to map to the view and update the existing charts on an interval?
Any help would be great!
Right now:
Service:
I’m implementing the interval here:
getSingleChartObsinterval(id: number, interval: number) : Observable<Graph> {
return Observable.interval(interval).flatMap(() => this.getSingleChartobs(id));
}
getSingleChartobs(id: number) : Observable<Graph> {
return this.jsonp.get(“api location”)
.map(response => this.extractJsonData(response, id) as Graph)
}
extractJsonData is just taking the response and manipulating it to work with the chart JS. It returns a Graph object that has properties that are easy to work with. I don’t have control of the API so I can’t reconfigure the response to include more than one graph.
The component:
import { Component } from '#angular/core';
import { ChartsModule } from 'ng2-charts/ng2-charts';
import { ChartService } from './chart.service';
import { Graph } from './graph';
import { OnInit } from '#angular/core';
import { Observable } from 'rxjs/Rx';
#Component({
selector: 'ab-chart',
styles: [`
.chart {
display: block;
}
`],
templateUrl: 'app/chart.component.html'
})
export class ChartComponent implements OnInit {
ngOnInit(): void {
console.log("Chart component init");
this.getSingleChart(3, 5000);
this.getSingleChart(5, 4000);
this.getSingleChart(6, 5000);
}
graph: Graph;
graphs: Graph[] = [];
constructor(
private chartService: ChartService
) {}
getSingleChart(id: number, interval: number): void {
this.chartService.getSingleChartObsinterval(id, interval)
.subscribe(x =>
this.graphs.push(x)
);
}
}
The view:
<div *ngFor="let graph of graphs" class="chart-container">
<base-chart
class="chart"
[datasets]="graph.datasets"
[labels]="graph.labels"
[options]="graph.options"
[chartType]="graph.type">
</base-chart>
</div>
Since each graph has its own id (I assume its unique) so I'd just change getSingleChart() method to update graphs object at specific key. Note I changed the graphs property from an array to an object:
graphs: {[key: number]: Graph} = {};
getSingleChart(id: number, interval: number): void {
this.chartService.getSingleChartObsinterval(id, interval)
.subscribe(x => this.graphs[id] = x);
}
get graphIds() {
return Object.keys(this.graphs);
}
Then in the template you need to iterate the array of keys (you can iterate the graphs object:
<div *ngFor="let id of graphIds" class="chart-container">
<base-chart
class="chart"
[datasets]="graphs[id].datasets"
[labels]="graphs[id].labels"
[options]="graphs[id].options"
[chartType]="graphs[id].type">
</base-chart>
</div>
Do you have a limited amount of charts? If you always have three you could leverage the combineLatest operator (if you have more you would have to use some form of recursion I guess).
In your component you could do the following:
this.graphs = this.getSingleChart(3, 5000).combineLatest(
this.getSingleChart(5, 4000),
this.getSingleChart(6, 5000),
(val1, val2, val3) => return [val1, val2, val3])
//.subscribe((arrayOfVal) => console.log(arrayOfVal);
This will return a new array every time one of the charts gets updated. If chart 2 gets a new value, the function (third argument of the combineLatest) will be called with the old value of 1, the new value for 2 and the old value of three.
In your template you could just be using this:
<div *ngFor="let graph of graphs | async" ...>
CombineLatest: https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/combinelatest.md
Related
I'm trying to create a Angular Material Table that displays dynamic data coming from an API endpoint but only the Table Header populates the screen without any data in it.
And it also does not throw any error...
Whenever I put hardcoded data, it works. But that's not what I want.
Here it is my table-component.ts file
import { Component, OnInit } from '#angular/core';
import { ApiService } from '../api.service'
import { MatTableDataSource } from '#angular/material/table';
const COUNTRY_DATA = []
#Component({
selector: 'app-table',
templateUrl: './table.component.html',
styleUrls: ['./table.component.scss']
})
export class TableComponent implements OnInit {
countryJSON;
displayedColumns: string[] = ['name', 'cases', 'deaths', 'recov', 'permill'];
dataSource = new MatTableDataSource(COUNTRY_DATA);
constructor(private apiService: ApiService) {
}
ngOnInit(): void {
this.apiService.getNews().subscribe((dataJSON) => {
this.countryJSON = dataJSON
for (let countryObject of this.countryJSON) {
COUNTRY_DATA.push
({
name: countryObject.country_name,
cases: countryObject.total_cases,
deaths: countryObject.total_deaths,
recov: countryObject.total_recov,
permill: countryObject.case_per_mill
})
}
console.log(COUNTRY_DATA)
})
}
}
As you can see, I'm printing to the console what does COUNTRY_DATA has in it, and I get the expected data: An array of objects
But they don't populate the Table... And it looks like this:
Well, the main problem with your code is that you don't push your array in the datasource, instead, you're instantiating an empty instance.
if you called dataSource = new MatTableDataSource(COUNTRY_DATA); at the position of console.log(COUNTRY_DATA) it should work.
A better approach when receiving an observable response is to use map instead of a loop, here is an answer for mapping
How to map a response from http.get to a new instance of a typed object in Angular 2
P.S. It will much better if you used an interface to introduce your object, and mapped the result in your ApiService and just set it as the value of your datasource in the component.
This question already has an answer here:
How to share data between components using a service properly?
(1 answer)
Closed 2 years ago.
hello i am using angular 8 and i would like to know how can i access the set value in any page ?
my code
class.ts
export class testClass {
get test():string{
return this.sexe;
}
set test(val:string){
this.sexe = val;
}
}
in clild.ts
import { testClass } from '../class';
export class Child{
constructor (private test:testClass){}
test (){
this.test.test = "hello";
}
in parent.js
import { testClass } from '../class';
export class Parent{
constructor (private test:testClass){}
test (){
console.log(test.test);
}
}
in app.module.ts
import { testClass } from '../class';
providers: [testClass],
what am i doing wrang to get "test undifined" in parent.js
Not to sure what you mean by setting and getting the value in any page? I'm assuming you mean component?
If so I'd use a service like so
#Injectable({
providedIn: 'root'
})
export class ExampleService{
private _value: any;
private _valueObs$ = new BehaviorSubject(null);
set setValue(newValue: any): void{
this._value = newValue;
}
get getNewValue(): any{
return this._value;
}
set setObservableValue(newValue: any): void{
this._valueObs$.next(newValue)
}
get getNewObservableValue(): any{
return this._valueObs$;
}
}
There are two approaches in the above method, the first is a pretty standard set and get, the second is utilising something known as a Subject, I'll touch on the difference in the next section.
To then use this service in any component
#Component({
selector: 'example',
})
export class ExampleComponent implements OnInit {
newValue: any;
constructor(private readonly exampleService: ExampleService
) { }
ngOnInit(): void {
this.getObservableExampleValue();
}
getExampleServiceValue(): any {
this.exampleService.getNewValue;
}
setExampleServiceNewValue(value: any): void {
this.exampleService.setNewValue = value;
}
getObservableExampleValue() {
this.exampleService.getNewObservableValue.subscribe((newObsValue) => {
this.newValue = newObsValue
})
}
setObservableExampleValue(value: any): void{
this.exampleService.setObservableValue(value);
}
ngOnDestroy(){
this.exampleService.getNewObservableValue.unsubscribe();
}
}
So I wont go into detail on the standard setValue & getNewValue, you can invoke them how you see fit.
Now the second approach is great if you want several components to be aware of a particular value at one time, so lets say we set the _valueObs$ with the setObservableValue method, and we have used this service in 5 different components, all 5 of those components will receive that value, very handy right?
Now you'll notice it's important that we actually invoke the getNewObservableValue so we can open the stream, normally you'd do this on the ngOnInit so the components template/code can have access to the value, assuming your looking to use the value straight away, otherwise you can invoke it at a later date, the way subscribing/observable's work is a bit like a tap.
Imagine you have a tap, and you turn it on - Known as subscribing
this.exampleService.getNewObservableValue.subscribe((newObsValue) => {
this.newValue = newObsValue
})
Well the tap is turned on and now emits a stream of water or again in this case a stream of data, so every time you set a new value, the new piece of data will come through that stream and will automatically update the this.newValue within your component.
But it's also important to turn the tap off! We don't want to be wasting water when we are done using it, this is when we unsubscribe when the component is no longer being used so
ngOnDestroy(){
this.exampleService.getNewObservableValue.unsubscribe();
}
This is to prevent what is known as a memory leak, which is beyond the scope of this answer, know to learn more about Rxjs I'd read some documentation - https://www.learnrxjs.io/ or watch some youtube videos there are plenty of tutorials out there!
Hopefully I've explained comprehensively enough if not feel free to comment.
You have to use a service.
The services are initialized when the app starts, and remain so until it stops. Passing a value through a service allows you to access it anywhere you call the service.
So if you had the following:
#Injectable()
export class ExampleService {
public varIWant: string = 'I wan't to use this anywhere.'
}
You can access it in your components, by doing:
import { ExampleService } from '../my/path/to/service'
export class Parent {
constructor(private exampleService: ExampleService) { }
public setVarAsLocal: string = this.exampleService.varIWant;
public changeServiceVariable() {
this.setVarAsLocal = 'New Value For String';
this.exampleService.varIWant = this.setVarAsLocal;
}
}
And that's it. As long as the instance is running the value will hold;
Following what is documented here: Dynamic Component Loader.
I want to know how is it possible to handle the data inside this HeroJobAdComponent class:
import { Component, Input } from '#angular/core';
import { AdComponent } from './ad.component';
#Component({
template: `
<div class="job-ad">
<h4>{{data.headline}}</h4>
{{data.body}}
</div>
`
})
export class HeroJobAdComponent implements AdComponent {
#Input() data: any;
}
As you can see, data is the object holding the data received. I want to be able to define a constructor for my HeroJobAdComponent class but if I do, the object data is undefined inside my constructor. I tried using ngOnChange instead which supposedly executes once input is changed from undefined to defined but it also did not execute at all.
Can someone please explain first why is the object undefined even though the data is defined in my main component calling it, and what's the workaround for this issue?
This is the constructor I am using:
constructor()
{
this.values = this.data.values;
this.spec_name = this.data.spec_name;
}
if you want to use any operation when you receive data in your component , you can use setter
export class HeroJobAdComponent implements AdComponent {
_data;
#Input() set data (data: any){
//operation on data goes here
this._data=data
};
get data() {
return this._data;
}
}
I'm using Angular Material Data Table in my project. The table is rendering with data
My problem is that I can't update automatically the view when I add new data to the database, every time I should refresh my page.
According to Cdk-table and after reading this tutorial I tried to add live data streaming that to table:
Here's my logique :
import { Component, OnInit } from "#angular/core";
import { MatTableDataSource } from "#angular/material";
import { AjoutprojService } from "../ajoutproj.service";
import { NouveauProjet } from "../models/nouveau-projet";
import { Observable } from "rxjs/Observable";
import 'rxjs/add/observable/merge';
import { DataSource } from "#angular/cdk/collections";
#Component({
selector: "app-liste-projets",
templateUrl: "./liste-projets.component.html",
styleUrls: ["./liste-projets.component.css"]
})
export class ListeProjetsComponent implements OnInit {
constructor( private ajoutProj: AjoutprojService ) {}
nouveauProjet: NouveauProjet[];
nouveauProjet2: NouveauProjet[];
stateExression: string = "inactive";
ngOnInit() {}
displayedColumns = ["Nom projet", "Lead Projet", "effectif"];
dataSource = new UserDataSource(this.ajoutProj);
applyFilter(filterValue: string) {
filterValue = filterValue.trim(); // Remove whitespace
filterValue = filterValue.toLowerCase(); // MatTableDataSource defaults to lowercase matches
//this.dataSource.filter = filterValue;
}
}
export class UserDataSource extends DataSource<any> {
constructor(private ajoutProj: AjoutprojService) {
super();
}
/*returns an observable that emits an array of data.
Whenever the data source emits data to this stream, the table will render an update.*/
connect(): Observable<NouveauProjet[]> {
return this.ajoutProj.getAllProj();
}
disconnect() {}
}
Here's my service
getAllProj(): Observable<NouveauProjet[]> {
return this.http.get<NouveauProjet[]>(
"http://127.0.0.1:8081/api/proj/projets"
);
}
ajoutProj.getAllProj() service is getting right data. but view is not live updating.
HttpClient doesn't stream. You're getting your data only once.
First you'd need a realtime database / backend solution, then you need to connect to that via websocket and listen to changes in the database.
Some frameworks / libraries that I like and package both the client- and serverside of the equation, and make the whole thing a lot easier:
Fireloop - built on top of Loopback 3 on nodejs, provides Angular SDK creation, ie. same models and APIs on client as on server. Typescript, Observables all the way. It's just awesome.
Firebase - "backendless", totally different way of thinking about a "server" from any REST scheme you might be used to.
Meteor - a monolithic framework, probably also very far from what you're used to.
Of course there's always another (very inefficient) way: Poll your DB every X seconds for changes.
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/switchMap';
export class UserDataSource extends DataSource<any> {
constructor(private ajoutProj: AjoutprojService) {
super();
}
connect(): Observable<NouveauProjet[]> {
const initialDelay = 0; // Time to wait before first poll, after the table has connected to this DataSource
const period = 10000; // Polling period in milliseconds
return Observable.timer(initialDelay, period)
.switchMap(() => this.ajoutProj.getAllProj());
}
disconnect() {}
}
I have an interesting setup in my template that uses values in an array to calculate and display a number
{{moduleProps.totals | values | sum | currency:'USD':true:'1.0-0'}}
Here is the values pipe
import { Pipe, PipeTransform } from '#angular/core';
import { values } from 'lodash';
#Pipe({
name: 'values',
})
export class ValuesPipe implements PipeTransform {
transform(value: any, ...args) {
if(value){
return values(value);
}
}
}
And here is the sum pipe
import { Pipe, PipeTransform } from '#angular/core';
import { reduce } from 'lodash';
#Pipe({
name: 'sum',
})
export class SumPipe implements PipeTransform {
transform(value: any, ...args) {
if(value){
return reduce(value,(a,b)=>a+b,0);
}
}
}
The array I'm using for this looks something like this...
[donation_earnings: 868, donation_pledges: 10, raffle_purchase_earnings: 4, fixed_purchase_earnings: 6, bid_earnings: 38]
When I try to go update one of these numbers later on like this...
this.moduleProps.totals.donation_pledges += events[0].amount;
Where the events[0].amount should be a number added to whatever the current number of that object property is.
When I do this, however, the template is not updating. When I log out the object property after doing the math I see the number is changed but the template doesn't hear about it.
After I do this I am also calling
this.changeDetectorRef.detectChanges();
To help with change detection on another array of items being pushed and popped but this doesn't seem to also be picking up the changes to the values in the array.
I have also tried running this in a NgZone.run() but that wasn't updating the template either.
Is there some sort of caveat I don't understand yet about the way angular 2 handles array change detection?
EDIT:
Also when I output the individual object properties to the page I see their numbers changing, it's only the number that's passed through the pipes that isn't updating