2

I have two angular service calls in which service call 2 should be made on the result of an value from the 1st service call. But my code is not hitting the second service call at all. Please find the code snippet below.

ngOnInit(): void { let personId: string = this._activatedRoute.snapshot.params['id'];

    this._personService.getPersonById(personId)
        .then(person => this.person = person);

    console.log(this.person); --prints undefined but displays data within the html correctly.

    if (this.person) { 
    this._teamService.getTeamById(this.person.teamId)
            .then(teamData => this.team = teamData);
    }
}
Auo
  • 173
  • 1
  • 3
  • 13
  • Thank you for the explanation, I did try as below and it errors as "Cannot read property 'teamId' of undefined" because the input value "this.person.teamId" is not available. this._teamService.getTeamById(this.person.teamId) .then(teamData => { if (this.person !== undefined) { this.team = teamData; } } ); – Auo Sep 25 '17 at 14:30
  • I am not able to think of other alternatives that could avoid the callback issue, any help would be greatly appreciated. – Auo Sep 25 '17 at 14:33
  • Thank you. I have added "console.log" to check the this.person.teamId value as I get an error on browser developer tools as "Cannot read property 'teamId'" . I have added await as you demonstrated it and it gives me a compilation error as "Cannot find name await" I am new to ANgular2 , can you please correct me. Thank you – Auo Sep 25 '17 at 15:33

2 Answers2

1

Add an await to your _personService.getPersonById since it returns a Promise.

The console.log will only be hit after the preceding await is complete.

    public async ngOnInit() {
        await this._personService.getPersonById(personId)
            .then(person => this.person = person)
            .catch(err => console.log(err));

        console.log(this.person); 

        if (this.person) { 
        await this._teamService.getTeamById(this.person.teamId)
                .then(teamData => this.team = teamData)
                .catch(err => {
                  // note curlys allow multiple statments.
                  // do work with error, log it etc..
                }
        }    
     }

As to why you see value in HTML and not console. HTML Data binding will show result of Promise as soon as it has the value. The console.log is being called immediately(some ms) after getPersonById and when not awaiting, it does not have the value at that exact moment..

This is how to accomplish the above by nesting the functions. Note the async person.

public async ngOnInit() {

   await this._personService.getPersonById(personId)
        .then(async person => {
         // To Use await inside .then, add async like above
         this.person = person; 
         await this._teamService.getTeamById(this.person.teamId)
            .then(teamData => this.team = teamData)
            .catch(err => console.log(err) );
        })
        .catch(err => console.log(err));

    console.log(this.person);

When you call a function that returns a promise, the then will be called after the first promise is resolved.. And subsequent .thens(you can add them). The thens always run synchronously.. Too much of this is called 'chaining' and is perceived as bad design. Thens are sequential/synchronous no matter if you use await or not.

If you want the outter functions to wait( or 'block') so as to run synchronously as well, you await them as well.

Last Edit: I suggest using the following copied from another SO answer to learn about async/await.

Using:

public delay(ms: number) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

Call the following and note the order of the output.. Add and change awaits to learn how it behaves.

public async someFcn() {
   console.log('1');
   this.delay(2000)
      .then(x => console.log('2'));
console.log(3)
await this.delay(1000)
   .then(x => console.log('4')
   .then(x => console.log('5');
console.log('Done');
}
ttugates
  • 4,315
  • 1
  • 33
  • 45
  • Thank you. I have added "console.log" to check the this.person.teamId value as I get an error on browser developer tools as "Cannot read property 'teamId'" . I have added await as you demonstrated it and it gives me a compilation error as "Cannot find name await" I am new to ANgular2 , can you please correct me. Thank you – Auo Sep 25 '17 at 15:31
  • You can enable the await keyword by making the function async. You do that by adding the `async` keyword between public and ngOnInit(). – ttugates Sep 25 '17 at 15:38
  • Thank you, its working now. few questions. My ngOnit() is actually calling two services person and team, the moment I made my function as async it wanted me to return a value instead of void. I was not sure if I should return /can return both but I tried with just as below and it works now. I am not sure if this actually correct way to write. `async ngOnInit(): Promise { await this._personService.getPersonById(personId) .then(person => this.person = person); .... return this.person; }` – Auo Sep 25 '17 at 15:50
  • So thats a little different question but, ngOnInit() should return void, the second call should be awaited as well. I will update my answer to reflect all changes including a catch statement and extra info.. Please consider marking my answer with the checkbox if you believe it answered your question. – ttugates Sep 25 '17 at 15:55
  • Cool. Thank you for all the information. yes, I have marked it. – Auo Sep 25 '17 at 16:52
  • From your code, teamservice call is waiting till the person service call returns a promise- Is my understanding right. – Auo Sep 25 '17 at 16:54
  • Thank you, it is clear now. But we use async calls and again to chain up we use await /delay to keep it waiting, indirectly we are making it sync. What I don't understand how are the async calls really helping in case of dependency within the code. – Auo Sep 26 '17 at 09:22
  • I was able to make it work without using the async and await as below, just by nesting `public ngOnInit() { let personId: string = this._activatedRoute.snapshot.params['id']; this._personService.getPersonById(personId) .then( person => { this.person = person; this._teamService.getTeamById(this.person.teamId) .then(teamData => this.team = teamData); });}` – Auo Sep 26 '17 at 10:55
0

Your service call is returning a Promise, which is asynchronous - The rest of your code will not be blocked by the call to then.

So your console prints undefined, the if check fails, and "then" at some future time your service call returns and your first then is executed (the second one is never executed because of your if).

You can fix this by putting your if statement into your then, or research some techniques to avoid "callback hell".

Zircon
  • 4,194
  • 14
  • 30