Closed BenevidesLecontes closed 8 years ago
You can build your own ngFor
directive and add events or whatever you need. See https://github.com/angular/angular/blob/27a7b51d99d8f1ef34c30e07d89a3c506095c5e5/modules/%40angular/common/src/directives/ng_for.ts for the NgFor
source.
Operation like "waiting for NgFor to finish" doesn't make much sense (and didn't make sense in ng1 either) and as such there are no plans to support it. Try exposing your functional use-case on one of the support forums so people can suggest a proper way to go abut your problem.
i do think there is a use case for this. Imagine an asynchronous model that holds a property with an array of pictures. The pictures will be *ngFor
ed into the template, and after that some javascript needs to turn it into an imageviewer (carousel/fancybox/slideshow).
Firing an event after receiving the asynchronous model is too early and will not ensure the loaded state of the images in the DOM.
I've thought about using the (load)
event for <img>
tags; that seems to work, however there is no such solution when the *ngFor
is iterating <div style="background-image...">
because those dont have a (load) event.
If there is a solution which doesnt require more than 5 lines I'd like to know. I've seen component-solutions around the web but they seem to complex to me
I also have a similar use case. I have a list of items that may include a component with a TinyMCE editor and which may be reordered. I use the the AfterViewInit to initially hook up a textarea to the TinyMCE editor, if present. However, upon reordering the list, the connection between the element and the library is broken. I'd like to run some kind of callback after ngFor is done rendering changes in order to remove/readd the TinyMCE instances.
As a hack, I can run a callback at the end of my function that reorders the array. I add a short setTimeout to increase the likelihood that the callback fires after the DOM has updates. However, that's suboptimal because it introduces unnecessary delays into the program and does not ensure the list is actually done rendering.
+1 We have custom scrollBar directive which have to estimate wrapper clientHight and scrollHeight after all the elements are rendered. And now we have to hardcoded it on last index === array.length -1. it is a workaround. I'd be nice to have some natural way of applying directives on container where ngFor is used to generate children nodes.
For Masonry lib, the heights of the elements need to be known upfront or recalculate after the ngFor has finished. I would also be interested in knowing about a workaround fort this.
@rcfrias copy the source of *ngFor
and add an event. There isn't much to it.
We found acceptable solution on StackOverflow: Template: <div #label *ngFor="...
Component: Class SuperComponent { ViewChild('label') public label: any; ... ngAfterViewInit() { this.handleEndOfNgfor(); this.label.changes.subscribe(()=>this.handleEndOfNgfor()); } private handleEndOfNgfor() console.log('hooray!'); } }
We found acceptable solution on StackOverflow: Template: <div #label *ngFor="...
Component: Class SuperComponent { ViewChild('label') public label: any; ... ngAfterViewInit() { this.handleEndOfNgfor(); this.label.changes.subscribe(()=>this.handleEndOfNgfor()); } private handleEndOfNgfor() console.log('hooray!'); } }
We found acceptable solution on StackOverflow: Template:
<div #label *ngFor="...
Component:
Class SuperComponent {
ViewChild('label')
public label: any;
...
ngAfterViewInit() {
this.handleEndOfNgfor();
this.label.changes.subscribe(()=>this.handleEndOfNgfor());
}
private handleEndOfNgfor()
console.log('hooray!');
}
}
@zoechi do you mean, clone the directive at '@angular' and modify it? any link to how to do that? I've only found the TS files at @angular/common/src/directives/ng_for_of.d.ts but no clue how to modify it directly, or are you talking about creating a custom directive, that overrides the former?
@kievsash , can you share the link to the stackoverflow on how to do that? BTW, your code looks like you are printing out 'hooray' on load, and for every label happens to be in the *ngFor, but not actually the "final" EndOfNgFor(), or maybe I am mistaken?
@kievsash @rcfrias ... it is was probably meant as this? https://plnkr.co/edit/orTvTRLgiZ4jD2fvjcuE?p=preview
@rcfrias It is hard to find link again. Just try) it works like I want - at the end on list render with new data.
@mlc-mlapis thanks for the plnkr, I was only aware of OnInit, and I tried the change event in the past, but didnt work for me. I think I was using it the wrong way, Im sure adding the subscribe will do the trick.
@rcfrias Nice to hear that it helps. 🍖
@kievsash I have used that solution but ran into a problem when trying to update DOM values, which leads to this issue. Basically, changing values inside ngAfterViewInit
is a no-no and need to be done earlier. However, this is a problem if you're waiting for an *ngFor
loop to end (as in this case) and need to perform further DOM manipulation once the loop is over and content is ready (show/hide content, add classes, etc.).
In short, it would be very nice to be able to fire events when a for loop is over or to be able to do things once I've finished rendering content. It's simple in React, Vue or any other lib. I don't understand why it is so problematic in Angular. This could literally be my ignorance, so if @pkozlowski-opensource (or someone else) could explain a non-hack way of triggering an event once all DOM content is loaded within a component, I would very much appreciate it.
@patoncrispy Yes, changing data which affects template in ngAfterViewInit leads to the issue you've mentioned. but only for first run. So you can:
Move first run somewhere ngAfterViewInit() { this.handleEndOfNgfor(); //first run, sync, can cause issue if contains code with template data modification. this.label.changes.subscribe(()=>this.handleEndOfNgfor()); //Async, should not cause issue }
Wrap it in some async (Promise.resolve().then(() => this.handleEndOfNgfor();)) Maybe requestAnimationFrame or setTimeout,0 ngAfterViewInit() { rquestAnimationFrame(() => this.handleEndOfNgfor()); //It is async now, should not cause issue (I didn't have time to try now, plz check yourself) this.label.changes.subscribe(()=>this.handleEndOfNgfor()); //Async, should not cause issue }
Make event yourself for class SuperClass { public htmlChanged = new EventEmitter(true); // true for async emitting
ngAfterViewInit() { this.label.changes.subscribe(()=>this.htmlChanged.emit('changed')); //Async, should not cause issue } }
And of cause native event would be best solution :-)
How about this?
<div *ngIf="!contentPrinted">Rendering content...</div>
<ul [class.visible]="contentPrinted">
<li *ngFor="let item of items; let last = last">
...
<ng-container *ngIf="last && !contentPrinted">
{{ onContentPrinted() }}
</ng-container>
<li>
</ul>
onContentPrinted() {
this.contentPrinted = true;
this.changeDetector.detectChanges();
}
@peakseeker onContentPrintet will also be run on other events. Take a look at here https://stackblitz.com/edit/angular-9bh1gr
This issue has been automatically locked due to inactivity. Please file a new issue if you are encountering a similar or related problem.
Read more about our automatic conversation locking policy.
This action has been performed automatically by a bot.
There's a way to do something after ngFor? I'm trying to activate carousels after ngFor but it won't work without setTimeout and if i have 2 or more carousels with ngFor, the timeout work on first but in others carousels it don't work. I tried this suggestion and it doesn't work neither: http://stackoverflow.com/questions/35819264/angular-2-callback-when-ngfor-has-finished. Sorry for posting here, but i can't find this anywhere.