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.
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.
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.
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- ...
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
ngIf
becomes truthy, you will be notified by the ViewChildren
subscription2. on initialization ngFor
element is rendred regardless of ngIf
on it or it's parent
ViewChildren
subscription will not notify you for the first time, but you can be sure it's rendered in the ngAfterViewInit
hook3. items are added/removed to/from the ngFor
Array
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');
}
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With