7

I have a service used as a local data store to share data across the app. This service makes a few rest calls in the beginning to initialize some data. It look like this:

Injectable()
export class DataStoreService {
     leadList: LeadModel[]
     optyList: ContactModel[]

     constructor(public dataService:DataService){
         this.initializeData()
     }

     initializeData() {

         this.dataService.getObjectData('opportunities').subscribe(
             (data) =>  this.optyList = data
         )

         this.dataService.getObjectData('leads').subscribe(
             (data) =>  this.leadList = data
         )
     }
}

I have a component page where I do below:

ngOnInit() {
     for(let lead of this.dataStore.leadList){
             if(lead.accepted == false)
              this.newLeadsList.push(lead)
     }
}

It is very obvious that if initialize data fails to finish the leadList may be undefined and this ngOnInit for loop will crash as well. So, how do I wait in the ngOnInit until initializeData finishes?

Jason Aller
  • 3,541
  • 28
  • 38
  • 38
Vik
  • 8,721
  • 27
  • 83
  • 168

2 Answers2

4

remove initializeData() from constructor and do something like this:

public async initializeData()
{
   this.optyList = await this.dataService.getObjectData('opportunities').toPromise();
   this.leadList = await this.dataService.getObjectData('leads').toPromise();
}


async ngOnInit() 
{
     await initializeData();

     for(let lead of this.dataStore.leadList){
         if(lead.accepted == false)
         this.newLeadsList.push(lead)
     }  
}  

I made code from head so it can have some bugs - may be async should be also before function ngOnInit()... check this.

Async-await-toPromise make that your asynchronous function behaves as synchronous function... and JS wait until your toPromise finish before execute another promise...

UPDATE

If I understand you right: you wanna call your service "DataStoreService.initializeData()" once and use it in future in other components (without calling initializeData again) - ? - if yes then you need Service singleton (which can also use inside above async-await technique)

Kamil Kiełczewski
  • 85,173
  • 29
  • 368
  • 345
  • i do not see it a good option to call initializeData() in all the pages of my app. there are may pages showing different data. – Vik Jan 25 '18 at 21:28
  • I don't uderstand - why you say that you want(or not want) to call initializeData in all pages? – Kamil Kiełczewski Jan 25 '18 at 21:30
  • well if i say 20 pages. i dont want to do initializeData() in ngInit of all pages. what i want is that i can just do a call to this.data.leadList and if it is not initialized then my code can wait there – Vik Jan 25 '18 at 21:32
  • do I understand you right: you wanna call your service DataStoreService. initializeData() once and use it in future in other components (without calling initializeData) - ? - if yes then you need Service singleton https://stackoverflow.com/q/36198785/860099 – Kamil Kiełczewski Jan 25 '18 at 21:39
  • you got it right and i am doing that. however still the first call using those lists still need to make sure that the lists in the singleton service are initialized – Vik Jan 25 '18 at 22:00
  • you don-t need because await function will WAIT automatically until async call finish. This mean that before initializeData() will finish you can be sure that data in lists this.optyList and this.leadList are loaded. – Kamil Kiełczewski Jan 25 '18 at 22:06
  • 1
    `for` loop must be call after `initializeData()` resolves. Check this https://repl.it/repls/MediumorchidMixedCatfish – ponury-kostek Jan 26 '18 at 13:27
  • @ponury-kostek - thanks for fixing bugs - I made code from "head" :P – Kamil Kiełczewski Jan 27 '18 at 21:26
  • @ponury-kostek - however I also improve your solution :) `for` loop not need to be called in 'nested' way - I update code in my answer by adding `async` and `await` in `OnNgInit()` – Kamil Kiełczewski Jan 29 '18 at 13:44
  • @KamilKiełczewski Ofc :) I didn't say that `for` MUST be in `then` callback, only that it must be called after `initializeData` – ponury-kostek Jan 29 '18 at 14:52
2

What you can utilize is observables, in this case a BehaviorSubject which you subscribe to and always emits if there is a subscriber. After making the initial request, just call next and all components that are subscribing will get the value... when it eventually arrives:

DataStoreService:

import {BehaviorSubject} from 'rxjs/BehaviorSubject';

// ...

leadList: LeadModel[]
leadList$ = new BehaviorSubject<LeadModel[]>([])

constructor(public dataService:DataService){
  this.initializeData()
}

initializeData() {
  this.dataService.getObjectData('leads')
    .subscribe((data) =>  {
      this.leadList = data;
      this.leadList$.next(this.leadList)
    })
}

Then in your components, subscribe to leadList$:

leadList = <LeadModel[]>[];

ngOnInit() {
  this.dataStore.leadList$.subscribe(data => {
    this.leadList = data;
    // do your magic here :)
  })
}

Then remember to unsubscribe when component is destroyed!

AT82
  • 71,416
  • 24
  • 140
  • 167