user5738822
user5738822

Reputation:

execute a function when *ngFor finished in angular 2

I trying to create a application with angular 2,i want when last element rendered in *ngFor,execute a function, somthing like this :

<ul>
  <li *ngFor="#i of items">{{i.text}}</li> <==== i want when this completed, execute a functuon in my class
</ul>

Thanks.

Upvotes: 49

Views: 40803

Answers (8)

itdev
itdev

Reputation: 53

I used this technique which worked well for my case:

<div *ngFor="let item of itemList; let i = index;">
     {{ ( (!flag) && ( (i+1) ===  itemList.length) ) ? myFunction() : ''}}
</div>

In my .js code :

let flag : boolean = false;

myFunction()
{
   // To execute if ngFor has finished
   this.flag = true;
}

Upvotes: 0

Ankit Singh
Ankit Singh

Reputation: 24945

Update

You can use @ViewChildren for that purpose

There are three cases

1. on initialization ngFor element is not rendred due to ngIf on it or it's parent

  • in which case, whenver ngIf becomes truthy, you will be notified by the ViewChildren subscription

2. on initialization ngFor element is rendred regardless of ngIf on it or it's parent

  • in which case ViewChildren subscription will not notify you for the first time, but you can be sure it's rendered in the ngAfterViewInit hook

3. items are added/removed to/from the ngFor Array

  • in which case also ViewChildren subscription will notify you

[Plunker Demo] (see console log there)

@Component({
  selector: 'my-app',
  template: `
        <ul *ngIf="!isHidden">
          <li #allTheseThings *ngFor="let i of items; let last = last">{{i}}</li>
        </ul>
        
        <br>
        
        <button (click)="items.push('another')">Add Another</button>
        
        <button (click)="isHidden = !isHidden">{{isHidden ? 'Show' :  'Hide'}}</button>
      `,
})
export class App {
  items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];

  @ViewChildren('allTheseThings') things: QueryList < any > ;

  ngAfterViewInit() {
    this.things.changes.subscribe(t => {
      this.ngForRendered();
    })
  }

  ngForRendered() {
    console.log('NgFor is Rendered');
  }
}

Original

You can do it like this ( but see the side Effects for yourself )

<ul>
  <li *ngFor="let i of items; let last = last">{{i}} {{last ? callFunction(i) : ''}}</li>
</ul>

Which is Useless, unless used with changeDetectionStrategy.OnPush

Then you can have control over how many times change detection occurs, hence how many times your function is called.

i.e: You can trigger next changeDetection when the data of items changes, and your function will give proper indication that ngFor is rendered for real change.

Upvotes: 83

Manzer A
Manzer A

Reputation: 3826

Possible solution: the answer is displayed on https://stackblitz.com/edit/angular-phpauq?file=src%2Fapp%2Fapp.component.ts

app.component.html

<div [innerHTML]="html"></div>

app.component.ts

 html = '';
ngAfterViewInit(){
       let  i;
       this.html += '<ul>';
       for(i = 0 ; i < this.abc.length ; i++){
           this.html += '<li>';
           this.html +=  this.abc[i];
           if(parseInt(i) === this.abc.length - 1){
           this.callMethod(i);
           }
           this.html += '</li>';
           }
           this.html += '</ul>';
    }


    callMethod(text){
    alert(text);
    }

Upvotes: 0

Heru Phang Sebastian
Heru Phang Sebastian

Reputation: 11

I'd managed to perform some hacks to prevent event get triggred (I found that scrolling always trigger those events)

By adding : [Add in your component]

private globalFlag = -1;

and this is my event that will be triggered when looping is finished

ev(flag){
    if(flag != this.globalFlag){
      console.log("TRIGGER!")
      this.globalFlag = flag;

    }
  }

and this the looping code

 <div *ngFor="let var of list;let i=index;let last=last">
    {{last ? ev(i) : ''}}
</div>

The main idea is to prevent event be trigger if the flag was same. So it only be trigger when the globalFlag value differed with current flag that passed from ngFor. Sorry for my bad english, hope you can understand my idea.

Upvotes: 1

bdunn
bdunn

Reputation: 482

I ran into this problem as well. In my case I was calling a web service to retrieve data and needed to execute javascript to initialize each template produced by *ngFor. Here's what I came up with:

updateData() {
  this.dataService.getData().subscribe(
    function(data) {
      this.data = data;
      setTimeout(javascriptInitializationFunction, 0);
    },
    function(err) { /* handle it */ }
  );
}

setTimout will wait for the DOM To be updated before executing. This isn't exactly what you asked, but hopefully it helps.

Upvotes: 2

Methodician
Methodician

Reputation: 2506

I used a slight hack of the approach others have complained about and it worked like a charm:

<ul>
  <li *ngFor="let i of items; let last = last">{{i}} {{last ? callFunction(i) : ''}}</li>
</ul>

Then in your component:

shouldDoIt = true; // initialize it to true for the first run

callFunction(stuff) {
    if (this.shouldDoIt) {
        // Do all the things with the stuff
        this.shouldDoIt = false; // set it to false until you need to trigger again
    }
}

This doesn't address the root of the problem with this approach but it's an extremely low-cost hack that made my app run smoothly. I do hope the Angular team will implement a more friendly way to trigger some code after the *ngFor loads its content.

Upvotes: 9

edmond wang
edmond wang

Reputation: 44

I am facing the same question and find one way to walk around this. Not sure whether it is suitable for you or not.
Pre-conditions is you are using ngFor to render inbound properties.

context: I need call MDL's upgradeAllRegistered to make checkbox pretty after fetch grid data from back-end.

I add one setTimeout inside 'ngOnChanges';

grid.componet.ts:

export class GridComponent {
  @Input() public rows: Array<any>;
    ngOnChanges(changes: {[propertyName: string]: SimpleChange}) {
    for (let propName in changes) {
      if (propName == 'rows') {
        setTimeout(() => componentHandler.upgradeAllRegistered());
      }
    }
  }
}

grid.component.html:

<tr #rowDOM *ngFor="let row of rows;"></tr>

Upvotes: 1

Pardeep Jain
Pardeep Jain

Reputation: 86790

you can do the same by getting last index using #last of *ngFor and call function by getting last index value and do your stuff whatever you want. here is code for the same -

<ul>
   <li *ngFor="#item of items; #last = last">
    <span *ngIf='last'>{{hello(last)}}</span>
    {{item}}
   </li>
  </ul>


items: Array<number> = [1,2,3,4,5]
  constructor() { console.clear();}
  hello(a){
    if(a==true)
      this.callbackFunction();
  }
  callbackFunction(){
    console.log("last element");
  }

working example for the same Working Plunker

Upvotes: 3

Related Questions