0đź‘Ť
Try this instead, bringing the chart creation and rendering inside the async block of the observable:
cities: Observable<DataModel>;
TestVar: string;
constructor(private http: HttpClient) { }
ngOnInit() {
this.cities = this.http.get<DataModel>('./assets/data.json');
this.cities.subscribe(events => {
this.TestVar = events[0].District;
let chart = new CanvasJS.Chart("chartContainer", {
theme: "light2",
animationEnabled: true,
exportEnabled: true,
title: {
text: "Monthly Expense"
},
data: [{
type: "pie",
showInLegend: true,
toolTipContent: "<b>{name}</b>: ${y} (#percent%)",
indexLabel: "{name} - #percent%",
dataPoints: [
{ y: 450, name: this.TestVar },
{ y: 120, name: "Insurance" },
{ y: 300, name: "Traveling" },
{ y: 800, name: "Housing" },
{ y: 150, name: "Education" },
{ y: 150, name: "Shopping" },
{ y: 250, name: "Others" }
]
}]
});
chart.render();
});
}
đź‘Ť:1
Observables
are async
, which means you’re trying to use the value of this.TestVar
before your http request is finished and is therefore still undefined.
You can stay synchronous if you initialise the chart after the observable is finished:
cities: Observable<DataModel>;
TestVar: string;
constructor(private http: HttpClient) {
this.cities.subscribe(events => {
this.TestVar = events[0].District;
this.init();
});
}
ngOnInit() {
this.cities = this.http.get<DataModel>('./assets/data.json');
}
public init(): void {
let chart = new CanvasJS.Chart("chartContainer", {
theme: "light2",
animationEnabled: true,
exportEnabled: true,
title:{
text: "Monthly Expense"
},
data: [{
type: "pie",
showInLegend: true,
toolTipContent: "<b>{name}</b>: ${y} (#percent%)",
indexLabel: "{name} - #percent%",
dataPoints: [
{ y: 450, name: this.TestVar },
{ y: 120, name: "Insurance" },
{ y: 300, name: "Traveling" },
{ y: 800, name: "Housing" },
{ y: 150, name: "Education" },
{ y: 150, name: "Shopping"},
{ y: 250, name: "Others" }
]
}]
});
chart.render();
}
đź‘Ť:0
TL;DR
Call the code to create and render your chart
after you’ve got the data back from your http.get
call.
Longer version
TestVar
is undefined because you’re trying to use the value before it’s been populated by your subscribe
callback function.
Observable
s stream data asynchronously (not to be confused with async
/await
with promises). Have a read of the RxJs Docs to familiarize yourself with how they work and how to use them.
Your http.get
call is returning the data back after you’ve already created and rendered the chart. At which time when you’re creating/rendering, the TestVar
value is still undefined
, as it’s not yet been set.
You need to set your TestVar
value inside the callback, and then use the code you’re using to set up and render your chart, like so:
TestVar: string;
constructor(private readonly _http: HttpClient) { }
ngOnInit() {
this._http.get<DataModel>('./assets/data.json')
.subscribe(data => {
this.TestVar = data[0].District;
// code to render chart AFTER you've retrieved the value
});
}
Note that you don’t need to create and assign cities
class variable of Observable<DataModel>
, as you’re only using it locally and nowhere else, and can instead “chain” the call to returned Observable
to subscribe to it.
Note that it’s advisable to create and subscribe to things inside the ngOnInit()
, rather than the constructor. ngOnInit
lifecycle hook indicates that Angular has created the component and all the bindings are ready, so you’re not creating all the things inside it before Angular can render anything. Here’s a much better explanation on SO about the differences between a constructor
and ngOnInit
.
Better still:
You’re only using the TestVar
value once/locally, so I don’t see a need for setting it as a class variable (like with the cities
class variable).
Instead, just grab the value and use it inside your callback:
// no need for the TestVar inside the class
constructor(private readonly _http: HttpClient) { }
ngOnInit() {
this._http.get<DataModel>('./assets/data.json')
.subscribe(data => {
const district = data[0].District;
// use the district variable when setting up your chart
});
}
Even better still:
Extract your chart creation code into its own private function inside your class, which takes the District
value as an input, to keep your code nice and clean, rather than having a big long ngOnInit()
function.
It will make your code a lot more readable and easier to see what’s going on:
ngOnInit() {
this._http.get<DataModel>('./assets/data.json')
.subscribe(data => {
const district = data[0].District;
// then set up the chart
this.initializeChart(district);
});
}
private initializeChart(district: string) {
// your code to initialize chart, using the district param passed in
}