Angular .subscribe executes at the end after the ngOnInit

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.

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
}

Leave a comment