Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

execute a function when *ngFor finished in angular 2

People also ask

Can I call function in ngFor?

Do it in code and assign it to each item and then just use it inside *ngFor . Such function calls from template bindings are discouraged.

How do you break out of ngFor?

There is no option to break ngFor . You can use a custom pipe that doesn't return values after the element where the condition is met.

What is the purpose of * In ngFor in Angular?

In *ngFor the * is a shorthand for using the new angular template syntax with a template tag, this is also called structural Directive.It is helpful to know that * is just a shorthand to explicitly defining the data bindings on a template tag.

Can we use two ngFor in Angular?

You can't use multiple *ngFor s in the same element as those are structural directives and angular handles them in a specific way (basically for each structural directive it creates an ng-template element which then holds the directive itself, you can read more about it here: https://angular.io/guide/structural- ...


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.


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.


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


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.


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>

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.