0

I want to get data out of observable, i understand that observable is async so it is not making any sense, but is there any way(workaround to achieve it)? Or maybe making the observable synchronous?

My code:

namesList: string[] = [];

ngOnInit() {
  this.httpService.list(this.env.someurl).subscribe((items) => {
    this.namesList= items.map( item => item.name)
    console.log(this.nameList) //has data
  });
  console.log(this.nameList)  //empty array
}
hades
  • 4,294
  • 9
  • 46
  • 71
  • where do you want to use `this.nameList`? if you want to use in html template like in `ngFor` it's OK and works for you and if you want to use it in component use it in `subscribe` because it takes some time to load data from server – Alireza Ahmadi Aug 20 '21 at 05:01
  • Or you can create function to manipulate `this.nameList` but call it in the `subscribe`. But if you insist to access `this.nameList` out of the `subscribe` you need to use `async/await` – Alireza Ahmadi Aug 20 '21 at 05:02
  • I want to use it in angular material autocomplete, wanted to load data at early then display it in autocomplete – hades Aug 20 '21 at 05:03
  • Ok simply you can use `*ngIf="nameList && nameList.length > 0"` in your template – Alireza Ahmadi Aug 20 '21 at 05:12
  • Does this answer your question? [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) – R. Richards Aug 20 '21 at 10:47

4 Answers4

0

Does async/await work for ngOnInit()? There's only one emit from httpService() call.

async ngOnInit() {
  try {
    this.nameList = await this.httpService.list(this.env.someurl).pipe(
      map((items) => items.map(item => item.name)),
      take(1),  // ensure complete
      toPromise()
    )
  } catch (error) {
    console.error({error});
  }    
  console.log(this.nameList)  // has data
}
user16695029
  • 3,365
  • 5
  • 21
0

You are getting the data out of it. Your 2nd console.log returns an empty array because it gets called before any data has been retrieved. The steps are basically:

  1. send list call
  2. log list
  3. receive answer
  4. populate list
  5. log list (inside the subscribe)

From a practical perspective, there's no reason to try to make the process synchronous. If your code depends on the nameList data then a cleaner approach is to handle it within the subscribe. Otherwise you risk ending up with both higher complexity and lower performance and UX.

Akirus
  • 554
  • 4
  • 9
0

The code behavior is correct. If you need data, It depends on where you are accessing the data. There are multiple scenarios,

  1. If you are accessing the data in HTML template, then wait for the data to be there by checking the data constraints (using ngIf).
  2. If you want to access it in the component then you can call a function inside a subscription, so that after every subscription you can access the data or the other way is using ngOnChanges(changes: SimpleChanges) method which will be bound to every changes in your directive.
0

If you want to use this data within the template (to be used with Material Autocomplete, as you answered in the comments), I think you can convert your list to Observable<string[]> then use it directly within the template (with async pipe) like the following:

Component Class (TS file):

namesList$: Observable<string[]>;

ngOnInit() {
  this.namesList$ = this.httpService
    .list(this.env.someurl)
    .pipe(map((items) => items.map((item) => item.name)));
}

Component Template (HTML file):

<mat-autocomplete #auto="matAutocomplete">
  <mat-option *ngFor="let name of namesList$ | async" [value]="name">
    {{ name }}
  </mat-option>
</mat-autocomplete>
Amer
  • 6,162
  • 2
  • 8
  • 34