1

I'd like to execute some logic exactly after ngOnInit() because i want to decouple the logic from page rendering. I thought of Angular lifecycle hooks so i put the logic in ngAfterViewInit(), but seems it get called at the same time as ngOnInit().

The code sample:

export class SampleComponent implements OnInit, AfterViewInit{

  list = [];

  ngOnInit() {

    this.localDataService.getData().then( res =>{ //Promise call
      for (let o in res) { 
      this.list.push(res[o]) //the list get loaded
     })

   }


  ngAfterViewInit() {  // the intention is to process the list after it's loaded

   this.remoteDataService.getData().then(res => {

   for (let o in res) { 
     itemFound = this.list.find( (itemInList) => {return itemInList.id === res[o].id})

     //and some code to manipulate the item found from list
     itemFound = ....  // but the itemFound is always 'undefined'

      })

    }

}

According to Angular lifecycle hooks, the ngAfterViewInit() should be execute after ngOnInit(), but the code running result tells otherwise. The itemFound in ngAfterViewInit() is always 'undefined'.

I tried to put the code block from ngAfterViewInit() to ngOnInit(), after the promise call, the itemFound will get proper value.

Could anyone help explain where went wrong? Is it because the Promise call is Asynchronous?

Wayne Wei
  • 2,907
  • 2
  • 13
  • 19
  • Why don't you create a simple function and call it inside the getData() promise? – MonkeyScript Mar 29 '21 at 05:39
  • I was doing exactly like you mentioned, but i found that the promise `remoteDataService.getData()` affect the page rendering speed significantly since it's a remote call and takes a lot of time. that is why i wanted to decouple them. – Wayne Wei Mar 29 '21 at 05:42

3 Answers3

2

According to Angular lifecycle hooks, the ngAfterViewInit() should be execute after ngOnInit(),

It is.

but the code running result tells otherwise.

It doesn't. You are not assigning the list in ngOnInit, but in a promise callback. That callback will be invoked once the promise gets resolved, but ngOnInit() doesn't wait for that to happen. (Even if angular wanted to wait, it couldn't, since angular doesn't know that Promise object exists, let alone that you have added a callback to it - and since waiting would freeze the UI, angular doesn't want to wait).

If you want to wait until both promises have resolved, you must therefore ask the promises, not angular. The simple way to do this is:

ngOnInit() {
  Promise.all([
    this.localDataService.getData(),
    this.remoteDataService.getData()
  ]).then(([list, data]) => {
    // work with list and data
  });
}
meriton
  • 68,356
  • 14
  • 108
  • 175
1

Because both localDataService.getData and remoteDataService.getData are asynchronous, it is not sure which will resolved first, and the localDataService.getData may not resolved at the time when ngOnInit has completed.

So even if you call the remoteDataService.getData just after ngOnInit has completed, it is not sure if localDataService.getData is resolved when remoteDataService.getData will be resolved.

You have to check whether both localDataService.getData and remoteDataService.getData are resolved. One solution is as below.

export class SampleComponent implements OnInit, AfterViewInit{

  localList = [];
  remoteList = [];
  
  localListLoaded = false;
  remoteListLoaded = false;

  ngOnInit() {

    this.localDataService.getData().then( res =>{ //Promise call
      for (let o in res) { 
        this.localList.push(res[o]) //the list get loaded
      }
      localListLoaded = true;
      manipulate();
    });

   
    this.remoteDataService.getData().then(res => {
      for (let o in res) {
        this.remoteList.push(res[o]);
      }
      remoteListLoaded = true;
      manipulate();
    }

  }
  
  manipulate() {
    if (localListLoaded && remoteListLoaded) {
      // some code stuff for remoteList and localList      
    }
  }

}
N.F.
  • 3,844
  • 3
  • 22
  • 53
  • I read both your and meriton's comments, actually you are telling the same thing which is also correct - **the timing of Promise call resolved**. But i really want to do the remote call after view init, so that it will not cause latency. And your solution ignited another idea, the listener to a variable. Thanks! – Wayne Wei Mar 29 '21 at 06:17
0

I realized this with BehaviorSubject from Rxjs lib, which basically is an observable pattern. The point is to notify code in ngAfterViewInit() to start only when the list data is ready.

Code sample:

export class SampleComponent implements OnInit, AfterViewInit{

  list = [];
  private listSub = new BehaviorSubject([]);
  listSubObservab = this.listSub.asObservable();

  ngOnInit() {

    this.localDataService.getData().then( res =>{ //Promise call
      for (let o in res) { 
      this.list.push(res[o]) //the list get loaded
       }
      this.listSub.next(this.list) //notify when list is ready
     })

   }


  ngAfterViewInit() {  // the intention is to process the list after it's loaded

    this.listSubObservab.subscribe(listLoaded => { //observer here, only triggered by notify

      this.remoteDataService.getData().then(res => {

      for (let o in res) { 
        itemFound = listLoaded.find( (itemInList) => {return itemInList.id === res[o].id})

      //and some code to manipulate the item found from list
      itemFound = ....  // Finally the itemFound has proper value

       }
      })

    }
 }

}
Wayne Wei
  • 2,907
  • 2
  • 13
  • 19