-1

I am new to angular. I am trying the below code to populate a pie chart. I am assigning the values inside the constructor. But here the method .subscribe executes at the end after the ngOnInit() executes. And it displays undefined as the value of this.TestVar

cities: Observable<DataModel>;
TestVar: string;

constructor(private http: HttpClient) {

this.cities = this.http.get<DataModel>('./assets/data.json');

    this.cities.subscribe(events => {
      this.TestVar = events[0].District;
    });
}
ngOnInit() {
    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();
}

I tried adding the below code inside ngOnInit() even. But it didnt fix my issue.

this.cities.subscribe(events => {
      this.TestVar = events[0].District;
});

Appreciate if anyone can help me with this.

Isuru99
  • 11
  • 1
  • 1
    Don't you need to render the chart _after_ you've got the data back from the subscription? – Geoff James Apr 29 '20 at 10:37
  • 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` –  Apr 29 '20 at 10:37
  • Isuru, put all the code in ngOnInit INSIDE subscribe function, and move the this.cities.subscribe to ngOnInit – Eliseo Apr 29 '20 at 10:41

3 Answers3

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

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();
  });

}
John McArthur
  • 916
  • 1
  • 12
  • 30
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.

Observables 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
}
Geoff James
  • 3,122
  • 1
  • 17
  • 36