0

I'm newbie with Angular.
I'm trying to save data in a class to read them into a component. I noticed that before the class is completely read, the component hurries the results, which have not yet been produced, then printing an error message:

Console output

Only later it prints the values correctly. But only and exclusively within the subscribe, as can be seen from the image.
This is my class:

import { Injectable } from '@angular/core';
import { WeatherForecastApiService } from '../weatherForecastApiService/weather-forecast-api.service';

@Injectable({
    providedIn: 'root',
})
export class WeatherClass {
    public weather: WeatherFeature;

    constructor(
        private wfas: WeatherForecastApiService,
    ) {
        this.wfas.getItalyWeatherData('Pisa').subscribe((response) => {
            const ks: string[] = ['name', 'main', 'temp'];
            this.weather = {
                cityName: response[ks[0]],
                degrees: response[ks[1]][ks[2]] - 273.15,
            }; 
            console.log('clean print', this.weather.cityName);
            console.log('clean print', this.weather.degrees);
        });
    }
    public showValues() {
        console.log('undefined print in component', this.weather.cityName);
        console.log('undefined print in component', this.weather.degrees);
    }
}

And this is my (premature) component:

import { AfterViewInit, Component } from '@angular/core';
import { WeatherClass } from '../weatherObject/weather-class';

@Component({
    selector: 'app-weather-forecast',
    templateUrl: './weather-forecast.component.html',
    styleUrls: ['./weather-forecast.component.scss'],
})
export class WeatherForecastComponent implements AfterViewInit {
    constructor(
        private weather: WeatherClass,
    ) {}
    ngAfterViewInit() {
        this.weather.showValues();
    }
}

I remember that something similar happened to me with javascript, but the context was easier to solve (that it was asynchronous?).
I am very curious to clear this question.

Memmo
  • 298
  • 3
  • 8
  • 31
  • Possible duplicate of [How do I return the response from an Observable/http/async call in angular?](https://stackoverflow.com/questions/43055706/how-do-i-return-the-response-from-an-observable-http-async-call-in-angular) – Igor Nov 06 '19 at 13:29
  • Possible duplicate of [How do I return the response from an asynchronous call?](https://stackoverflow.com/q/14220321/1260204) – Igor Nov 06 '19 at 13:30
  • The simplest way would be to use your api class directly and set your "class" values in your component. Right now it looks like you're trying to use it like a service but using it like a class and these concepts are similar but not exactly the same in the angular world. I gave an example solution below, but there would be many other ways to solve it :) – Jessy Nov 06 '19 at 13:31

2 Answers2

2

this.wfas.getItalyWeatherData is an observable and is asynchronous so there it is likely that your cityName and degree is never set by the time showValue() method is called even though you have it in ngAfterViewInit.

A solution would be for you to have a subject in your WeatherClass which you could listen to (by subscribing to it) inside of your WeatherForecastComponent component. (consider BehaviorSubject as they hold a default value and let you retrieve a value on first subscription)

@Injectable({
    providedIn: 'root',
})
export class WeatherClass {

    public weatherFeature = new BehaviorSubject<WeatherFeature>(undefined);

    constructor(
        private wfas: WeatherForecastApiService,
    ) {
        this.wfas.getItalyWeatherData('Pisa').subscribe((response) => {
            const ks: string[] = ['name', 'main', 'temp'];
            // This emits the new values for use to any subscribers.
            this.weatherFeature.next({
                cityName: response[ks[0]],
                degrees: response[ks[1]][ks[2]] - 273.15,
            }); 
        });
    }
}
@Component({
    selector: 'app-weather-forecast',
    templateUrl: './weather-forecast.component.html',
    styleUrls: ['./weather-forecast.component.scss'],
})
export class WeatherForecastComponent implements AfterViewInit {
    public weather: WeatherFeature;

    constructor(
        private weatherClass: WeatherClass,
    ) {}

    ngAfterViewInit() {
        // the subscription here will get the value or any future emitted values.
        this.weatherClass.weatherFeature.subscribe(
            (weatherFeature) => this.weather = weatherFeature
        );
    }
}

So in this example in your component i added a property to hold the value you where trying to get.

in your weather class I've added a behavior subject so you can subscribe to this to know when you can get a value for use.

To keep things named cleanly I recommend renaming your WeatherClass to WeatherService :) check out Angular Style guide when you have questions it's a fantastic resource to get used to the framework.

https://angular.io/guide/styleguide#service-names

Jessy
  • 1,150
  • 16
  • 27
  • I'd say that it is a very good solution. I'd like to ask you something else... What you did inside `next`, is to create an `interface` on the fly? – Memmo Nov 06 '19 at 14:12
  • 1
    The `this.weatherFeature.next(...);` code in the next is a literal object that is the same "shape" as your `WeatherFeature` class. You could use an interface as the type instead or you could use an instance object `this.weatherFeature.next(new WeatherFeature( /* some args here */))` :) – Jessy Nov 07 '19 at 17:33
  • 1
    I found this solution very useful. He gave me input to study 'BehaviorSubject' thoroughly! :) – Memmo Nov 08 '19 at 07:45
1

The issue here is that getItalyWeatherData does not complete before you call showValues. A common approach would be something among the lines of:

WeatherClass ->

getItalyWeatherData() {
  return this.wfas.getItalyWeatherData('Pisa');
}

WeatherForecastComponent ->

ngOnInit() {
  this.weather.getItalyWeatherData().subscribe(res => {same code as the one you currently have});
}

This way the moment the API call resolves the data will be displayed.

Akirus
  • 554
  • 4
  • 9
  • It works, ok... but it's not strictly what I want. I accepted [this answer](https://stackoverflow.com/a/58731264/8275210) because it is closer to the type of structuring of the code I was looking for. – Memmo Nov 06 '19 at 14:18