2

When I hit an api to get data from server in constructor and store it in local variable. I loop this variable in html to build table and show data in table. But when I call a function on api success and in it I loop the same variable which is used in HTML, the loop in function will run first and after that HTML loop. My requirement is to run HTML loop first and after its completion loop in function.

HTML CODE

<table>
   <tr *ngFor="let booking of jobHistory>
      <td>{{booking}}</td>
   </tr>
</table>

TYPESCRIPT CODE

jobHistory : any;    
constructor()
{
  this.service.getCustomerJobHistory().subscribe(   res => { this.jobHistory = res},
                                                    err => console.log(err),
                                                    () => this.setRating()
                                              );


}

setRating()
{
   //I want to run this loop after HTML loop completion.
   for(let booking of this.jobHistory)
   {
     console.log(booking);
   }
}
Lakhvir Singh
  • 664
  • 4
  • 15
  • 35
  • What's the *problem* you're having? What's the behaviour you want, and what you get instead? – jonrsharpe Jul 08 '17 at 11:00
  • Why would you want to run the setRating function after it renders the html? Can you go into more detail about that? It would seem more natural to want the setRating function to run first and then render the results to the html. – seescode Jul 08 '17 at 11:10
  • Try this implement the interface AfterViewInit and define the function ngAfterViewInit with setRating() https://angular.io/guide/lifecycle-hooks#afterview and you can also use ngOnInit like the same way – Sreemat Jul 08 '17 at 11:28
  • https://stackoverflow.com/questions/43457149/angular-4-what-is-the-right-way-to-wait-for-operation – yurzui Jul 08 '17 at 12:16

3 Answers3

3

Basically, you could use the 'last' operator of *ngFor

<table>
   <tr *ngFor="let booking of jobHistory; let l=last">
      <td>{{booking}} {{ l ? setRatings() : null}}</td>
   </tr>
</table>
Dan R.
  • 638
  • 5
  • 13
2
<table>
    <tr *ngFor="let booking of jobHistory; let last = last">
        <td>{{booking}}</td>
        <ng-container *ngIf="last">{{setRating()}}</ng-container>
    </tr>
</table>

setRating() {
   //I want to run this loop after HTML loop completion.
   for(let booking of this.jobHistory) {
       console.log(booking);
   }
}

The above solution will detect the last iteration of the ngFor loop. setRating will be called if the last index is not null.

Try this solution and let me know, if you need any help.

Edit: To avoid multiple execution problems,

lastValue:any;
setRating(last:any) {
   if (this.lastValue==last)
return;

   //I want to run this loop after HTML loop completion.
   for(let booking of this.jobHistory) {
       console.log(booking);
   }
   this.lastValue=last
}
Subtain Ishfaq
  • 793
  • 9
  • 16
  • Thank you so much. It worked properly but I have one more query how it will work when I load more data on scroll down. For example in first I got list of 20 but when I scroll down it loads more 20 entries. – Lakhvir Singh Jul 08 '17 at 11:28
  • Whenever the loop is loaded, the function will be called. On Scroll, Loop calls and span will be called. – Subtain Ishfaq Jul 08 '17 at 11:30
  • If solution worked for you, please accept the answere, to help others and let me know if you need any details – Subtain Ishfaq Jul 08 '17 at 11:30
  • Thank you so much. – Lakhvir Singh Jul 08 '17 at 11:36
  • 1
    I updated your answer a bit. I suggest using `ng-container` so that the dom isn't polluted with `span` elements. – malifa Jul 08 '17 at 11:59
  • And worth to mention: If angular is not run in prodMode `setRating` will be called multiple times depending on when you initialized / fill the array which is looped through. Initializing an array before `ngOnInit` is called for example will produce massive calls of that function. you should test this out in and out of prodMode! (better initialize an empty array and fill it when or after `ngOnInit` is called. – malifa Jul 08 '17 at 12:12
  • I am getting one issue. When I am hovering anywhere on table or icons it will calling function again and again. – Lakhvir Singh Jul 08 '17 at 12:12
  • Yes, this is the change detection. you probably have some hooks on hover. the same will probably happen if you click on your items, because click listeners are always on these elements by default even if you haven't set it up explicitely. And events will trigger the change detection, your `last` will be reevaluated and therefore the function is triggered again. – malifa Jul 08 '17 at 12:15
  • You can add `check` in your `setRating` and pass the `last` value to your function. if the `last` is changed then continue otherwise return – Subtain Ishfaq Jul 08 '17 at 12:19
2

So i saw the answers (including the accepted one) and while they are definitely worth knowing, i don't think it's the best way to handle your situation. The reasons already came up in the comments to the accepted answer:

  • When angular isn't run in prodMode you will get weird behaviour due to angulars double powered change detection if not run in prodMode. (Initializing and every change (even a single click on one of your elements) will your function twice due to change detection.

  • Even in prodMode depending on where your array is initialized, the function could be called multiple times. For example initializing your array like this:

    export class YourComp { jobHistory: any[] = ['one', 'two', 'three' ]; setRating() { ... } }

    will call the function three times after it's initialized, even in prodMode (which is very weird...).

  • Every triggerd event, will trigger the change detection which will end in reevaluating the last property and therefore call your function again. So clicking on one of your elements (even though you didn't even setup a click listener) will call setRating again.


Because of this, i suggest to try another approach. The probably easiest way i came up with would be to use another component.

Something like this:

export class MyTableComponent {
     @Input() data: any[];

     ngAfterViewInit() {
          this._setRating();
     }

     private _setRating() {
          console.log('set rating...');
     }
}

As its template it should have the same table you posted in your question. In your parent component you would initialize it with something like <my-table [data]="jobHistory" *ngIf="jobHistory"></my-table>

If your jobHistory data will be updated in your main component, you just clear it (which will destruct the MyTableComponent), push your new / updated elements into the array again, which will reinitialize the component and finally call setRating.

If setRating is called too early, a little setTimeout(() => { this._setRating(); }); would do.

Hope i could help.

malifa
  • 8,025
  • 2
  • 42
  • 57
  • Hi, this does not work well. If any another plugin for rating in angular2. My requirement is shown half rating and clicks on rating icon open a popup. – Harleen Kaur Arora Jul 18 '17 at 12:09