How do I return the response from an Observable/http/async call in angular?
Asked Answered
D

10

136

I have service which returns an observable which does an http request to my server and gets the data. I want to use this data but I always end up getting undefined. What's the problem?

Service:

@Injectable()
export class EventService {

  constructor(private http: Http) { }

  getEventList(): Observable<any>{
    let headers = new Headers({ 'Content-Type': 'application/json' });
    let options = new RequestOptions({ headers: headers });

    return this.http.get("http://localhost:9999/events/get", options)
                .map((res)=> res.json())
                .catch((err)=> err)
  }
}

Component:

@Component({...})
export class EventComponent {

  myEvents: any;

  constructor( private es: EventService ) { }

  ngOnInit(){
    this.es.getEventList()
        .subscribe((response)=>{
            this.myEvents = response;
        });

    console.log(this.myEvents); //This prints undefined!
  }
}

I checked How do I return the response from an asynchronous call? post but couldn't find a solution

Darg answered 27/3, 2017 at 20:17 Comment(6)
that would be a good point to emphasize on the fact it is not possible to transform an asychronous operation to a synchrnous-one.Luben
@Luben Thanks for the tip! I've tried to explain in the "What you shouldn't do:" section. Feel free to edit it.Darg
Does this answer your question? How do I return the response from an asynchronous call?Chimaera
@HereticMonkey that post is already credited in the answerDarg
@Darg And? Does that make the question any less of a duplicate?Chimaera
@HereticMonkey yes it does, because it's framework specific, it's observables. When I duplicated angular/rxjs, async questions with that answer, people got confused and said thening the observable doesn't work; this is obvious for angular answerers but not to you apparently. Also i thought it was enough to give the credit in the answer but i will change the question as well then.Darg
D
136

Reason:

The reason that it's undefined is that you are making an asynchronous operation. Meaning it'll take some time to complete the getEventList method (depending mostly on your network speed).

So lets look at the http call.

this.es.getEventList()

After you actually make ("fire") your http request with subscribe you will be waiting for the response. While waiting, javascript will execute the lines below this code and if it encounters synchronous assignments/operations it'll execute them immediately.

So after subscribing to the getEventList() and waiting for the response,

console.log(this.myEvents);

line will be executed immediately. And the value of it is undefined before the response arrives from the server (or to whatever that you have initialized it in the first place).

It is similar to doing:

ngOnInit(){
    setTimeout(()=>{
        this.myEvents = response;
    }, 5000);

    console.log(this.myEvents); //This prints undefined!
}

**Solution:** >So how do we overcome this problem? We will use the callback function which is the `subscribe` method. Because when the data arrives from the server it'll be inside the `subscribe` with the response.

So changing the code to:

this.es.getEventList()
    .subscribe((response)=>{
        this.myEvents = response;
        console.log(this.myEvents); //<-- not undefined anymore
    });

will print the response.. after some time.


**What you should do:**

There might be lots of things to do with your response other than just logging it; you should do all these operations inside the callback (inside the subscribe function), when the data arrives.

Another thing to mention is that if you come from a Promise background, the then callback corresponds to subscribe with observables.


**What you shouldn't do:**

You shouldn't try to change an async operation to a sync operation (not that you can). One of the reasons that we have async operations is to not make the user wait for an operation to complete while they can do other things in that time period. Suppose that one of your async operations takes 3 minutes to complete, if we didn't have the async operations then the interface would freeze for 3 minutes.


Suggested Reading:

The original credit to this answer goes to: How do I return the response from an asynchronous call?

But with the angular2 release we were introduced to typescript and observables so this answer hopefully covers the basics of handling an asynchronous request with observables.

Darg answered 27/3, 2017 at 20:17 Comment(3)
When a question is answered by the questioner at the exact same time of the post.. This is a good place to stop and write a blog post insteadMishamishaan
@JonasPraem True, but there is nothing wrong in sharing the knowledge on Stackoverflow. As you see the number of votes, it did help many people here and it will continue to do so.Luting
My question was somewhat similar. But this answer solved that too. :-)Stull
V
15

Making a http call in angular/javascript is asynchronous operation. So when you make http call it will assign new thread to finish this call and start execution next line with another thread. That is why you are getting undefined value. so make below change to resolve this

this.es.getEventList()  
      .subscribe((response)=>{  
       this.myEvents = response;  
        console.log(this.myEvents); //<-this become synchronous now  
    });
Vevay answered 20/5, 2017 at 6:13 Comment(0)
I
7

You can use asyncPipe if you use myEvents only in template.

Here example with asyncPipe and Angular4 HttpClient example

Indurate answered 18/2, 2018 at 20:56 Comment(2)
And (!) with this implementation one doesn't need to unsubscribe the Subscription: medium.com/@sub.metu/…Pupil
@SanJayFalcon Since this is a http request, there is no need for unsubscription.Barrows
T
3

Observables are lazy so you have to subscribe to get the value. You subscribed it properly in your code but simultaneously logged the output outside the 'subscribe' block. That's why it is 'undefined'.

ngOnInit() {
  this.es.getEventList()
    .subscribe((response) => {
        this.myEvents = response;
    });

  console.log(this.myEvents); //Outside the subscribe block 'Undefined'
}

So if you log it inside the subscribe block then it will log response properly.

ngOnInit(){
  this.es.getEventList()
    .subscribe((response)=>{
        this.myEvents = response;
        console.log(this.myEvents); //Inside the subscribe block 'http response'
    });
}
Telegraphic answered 23/9, 2018 at 10:37 Comment(0)
G
3

Here the problem is, you are initializing this.myEvents into subscribe() which is an asynchronous block while you are doing console.log() just out of subscribe() block. So console.log() getting called before this.myEvents gets initialized.

Please move your console.log() code as well inside subscribe() and you are done.

ngOnInit(){
    this.es.getEventList()
        .subscribe((response)=>{
            this.myEvents = response;
            console.log(this.myEvents);
        });
  }
Gusman answered 4/3, 2019 at 9:39 Comment(0)
B
3

The result is undefined because angular process async . you can trying as below:

async ngOnInit(){
    const res = await this.es.getEventList();
    console.log(JSON.stringify(res));
}
Brilliancy answered 9/3, 2019 at 13:32 Comment(0)
S
2

Also make sure that you map your response to a json output. Otherwise it will return plain text. You do it this like this:

getEventList(): Observable<any> {
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });

return this.http.get("http://localhost:9999/events/get", options)
            .map((res)=>{ return res.json();}) <!-- add call to json here
            .catch((err)=>{return err;})
}
Soave answered 27/3, 2017 at 20:27 Comment(0)
G
2

Undefined because the value here is logged before any data from the service is set from that above subscribe service call. So you have to wait until the ajax call finishes and set the data from the response data.

getEventList(): Observable<any>{
    let headers = new Headers({ 'Content-Type': 'application/json' });
    let options = new RequestOptions({ headers: headers });

    return this.http.get("http://localhost:9999/events/get", options)
                .map((res)=> res.json())
                .catch((err)=> err)
  }

Here make the Console log inside the subscribe method that will make the log when the data is set in myEvents variable.

ngOnInit(){
    this.es.getEventList()
        .subscribe((response)=>{
            this.myEvents = response;
     // This prints the value from the response
    console.log(this.myEvents)
        }); 
  }
Government answered 15/12, 2019 at 18:59 Comment(0)
A
2

To do this you have 2 options:

Suppose we have a service which is returning shipping details array :

  getShippingPrices(): Observable<IShippingDetails[]> {
    return this.http.get<IShippingDetails[]>('/assets/shipping.json');
  }

1. Use Async pipe : Easy way when you just want to show the result in template

In the component class directly assign the observable to variable:

export class ShippingComponent implements OnInit {
  shipOptions1 = this.cartService.getShippingPrices();
  constructor(private cartService: CartService) {}

  ngOnInit() {}
}

and then use async pipe in template :

<div *ngFor="let s of shipOptions1 |async">
  <label>{{s.type}}</label>
</div>

Refer: Check the 4th point in this URL https://angular.io/start/start-data#configuring-the-shippingcomponent-to-use-cartservice

2. Use Subscribe : When you want to manipulate it or want do some business logic on/from response

export class ShippingComponent implements OnInit {
  shipOptions2: IShippingDetails[] = [];
  constructor(private cartService: CartService) {}

  ngOnInit() {
    this.cartService.getShippingPrices().subscribe(response => {
      this.shipOptions2 = response;
      //console.log(this.myEvents);
      //All other code using shipOptions2
    });
  }
}

Admonition answered 4/8, 2021 at 18:52 Comment(0)
L
1

You can simply try this method-

let headers = new Headers({'Accept': 'application/json'});
let options = new RequestOptions({headers: headers});

return this.http
    .get(this.yourSearchUrlHere, options) // the URL which you have defined
    .map((res) => {
        res.json(); // using return res.json() will throw error
    }
    .catch(err) => {
        console.error('error');
    }
Libby answered 10/6, 2017 at 9:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.