angular / angular

Deliver web apps with confidence 🚀
https://angular.dev
MIT License
96.19k stars 25.46k forks source link

Event after ngfor? #8663

Closed BenevidesLecontes closed 8 years ago

BenevidesLecontes commented 8 years ago

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.

zoechi commented 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.

pkozlowski-opensource commented 8 years ago

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.

bert-w commented 7 years ago

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 *ngFored 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

ansorensen commented 7 years ago

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.

kievsash commented 7 years ago

+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.

rcfrias commented 7 years ago

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.

zoechi commented 7 years ago

@rcfrias copy the source of *ngFor and add an event. There isn't much to it.

kievsash commented 7 years ago

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!'); } }

kievsash commented 7 years ago

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!'); } }

kievsash commented 7 years ago

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!');
}
}
rcfrias commented 7 years ago

@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?

rcfrias commented 7 years ago

@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?

mlc-mlapis commented 7 years ago

@kievsash @rcfrias ... it is was probably meant as this? https://plnkr.co/edit/orTvTRLgiZ4jD2fvjcuE?p=preview

kievsash commented 7 years ago

@rcfrias It is hard to find link again. Just try) it works like I want - at the end on list render with new data.

rcfrias commented 7 years ago

@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.

mlc-mlapis commented 7 years ago

@rcfrias Nice to hear that it helps. 🍖

patoncrispy commented 7 years ago

@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.

kievsash commented 7 years ago

@patoncrispy Yes, changing data which affects template in ngAfterViewInit leads to the issue you've mentioned. but only for first run. So you can:

  1. 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 }

  2. 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 }

  3. 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 :-)

vulpfox commented 6 years ago

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();
  }

Original answer on SO

bumbummen99 commented 6 years ago

@peakseeker onContentPrintet will also be run on other events. Take a look at here https://stackblitz.com/edit/angular-9bh1gr

angular-automatic-lock-bot[bot] commented 5 years ago

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.