4

The class variable counties, doesn't hold on to the value assigned within the subscribe method on fetchCounties(). Logging the temp variable data returns a good list of counties, so does this.counties INSIDE of the subscribe, but as soon as I try access it outside of the subscribe it becomes undefined. Coming from a Java background and being new to Angular/Typescript this makes no sense to me...

public counties: ICounty[] = [];  

public getCounties(): ICounty[] {
        this.fetchCounties().subscribe(data =>{
          console.log("data");
          console.log(data);//logs correctly
          this.counties = data;
          console.log("counties inside subscribe " );
          console.log(this.counties);//logs correctly
        });
        console.log("counties outside of subscribe " );
        console.log(this.counties);//logs incorrectly (empty) >:0
        return this.removeInvalidCounties(this.counties); //passes empty counties list...
}
Anden
  • 43
  • 6
  • The `susbscribe` runs asynchronously, it runs when `fetchCounties` emits. – AliF50 Jan 30 '20 at 19:07
  • Does this answer your question? [How do I return the response from an asynchronous call?](https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) – John Montgomery Jan 30 '20 at 19:09
  • Does Java not have async functions? I'm not an expert but I'd assume they work similarly there. – John Montgomery Jan 30 '20 at 19:10
  • Newst java versions might, but I am use to Java 7/8 with legacy applications. I just find it extremely odd not being able to assign a class variable to a value wherever I need to. – Anden Jan 30 '20 at 19:20
  • The problem isn't that the code isn't being run inside the subscribe, the problem is that this.counties' value is being set inside the subscribe but nothing knows about that until its too late. Its a state problem I suppose. So what I understand is the calling code continues to execute even if the subscribe isn't finished? How can I force the subscribe to finish before other code is executed? – Anden Jan 30 '20 at 19:40
  • @Anden You have to do whatever you want to do with the data inside the `subscribe` block (or, like Ali's answer suggests, you can pass back the entire observable and let whatever receives it handle the subscribing, but you would still have to do any work inside `pipe` or `subscribe`). – John Montgomery Jan 30 '20 at 22:30

2 Answers2

1

To fix this, you can do:

  public getCounties(): Observable<ICounty[]> {
    return this.fetchCounties().pipe(
       map(counties => {
         // write your logic here in what makes a county valid, I will say it has to have a name
        return counties.filter(county => county.name);
      }),
    );
  }

Then you can consume it as an observable:

this.countiesService.getCounties().subscribe(counties => {...});

Then in your component you can do:

counties: ICounty[];

constructor(private countiesService: CountiesService) {}

ngOnInit() {
  this.countiesService.fetchCounties().subscribe(counties => this.counties = counties);
}

The html can then be:

<li *ngFor="let county of counties">{{ county }}</li>

Or better yet, use the Async pipe to automatically unsubscribe when the view is destroyed:

counties$ = this.countiesService.fetchCounties();

Then in the HTML:

<li *ngFor="let county of counties$ | async">{{ county }}</li>

================ With regards to your question, how can you wait for the subscription to finish before proceeding, you can convert it to a promise although I don't recommend this way.

public counties: ICounty[] = [];

// Typescript might complain here, saying an async function has to return a promise
public async getCounties() { 
  this.counties = await this.fetchCounties().pipe(take(1)).toPromise();
  return this.removeInvalidCounties(this.counties);
}
AliF50
  • 16,947
  • 1
  • 21
  • 37
  • 1
    The problem is that I want to do all of the retrieving and filtering of the county list inside the CountyService class and simply expose a getCounties() method that can be used to assign a variable in a component anywhere to a list of valid counties so I can abstract away all the minutia from client use. – Anden Jan 30 '20 at 19:57
  • Can you explain why wouldn’t use the promise approach? – Anden Jan 30 '20 at 23:08
  • One reason is that TypeScript will complain that an async function has to return a promise. Personally, I don't like mixing promises with observables. I like to just use Observables since they are more reactive. Promises are one and done. In this case, it is an HTTP call, so it doesn't make a difference but I like to stay in the observable world. – AliF50 Jan 31 '20 at 01:39
-1

That is because you are trying to access the array before the async call finished (fetchCounties).

public counties: ICounty[] = [];  

public getCounties(): ICounty[] {
  // step 1
  this.fetchCounties().subscribe(data =>{
  // step 3 because is asynchronous and it depends on the time of the call required.
  });
  // step 2
  return this.removeInvalidCounties(this.counties);
}

I don't know what are you trying to do. But probably, you should excecute removeInvalidCounties in the subscription.

UPDATE: if you are trying to filter the countries you are trying to use. You could do this.

public getCounties(): ICounty[] {
  this.fetchCounties()
  .pipe(map(countries => this.removeInvalidCounties(counties)))
  .subscribe(data =>{
    this.counties = data ;
  });
}