在Angular 2中,当*ngFor循环完成时执行一个函数

47

我正在使用Angular 2创建一个应用程序,我希望当*ngFor中的最后一个元素被渲染时执行一个函数,类似于以下代码:

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

你实际上想要什么是 *ngFor 的最后一个元素?你想在最后一个元素上执行像 *ngIf 这样的一些操作吗? - Pardeep Jain
1
我想调用滑块函数...我尝试了ngAfterViewinit或类似的方法,但没有起作用...当我使用setTimeout()时它可以工作,但这不是一个好主意... 你认为我应该怎么做? - user5738822
哦,不,使用 setTimeOut 不是一个好主意。这是一个非常有趣的问题,稍等我几分钟,我会尝试给你答案。 - Pardeep Jain
你想在列表渲染后仅调用一次方法吗?或者如果您在列表中添加/删除项目,它应该再次被调用吗? - mcgraphix
8个回答

78

更新

你可以使用@ViewChildren来实现这个目的。

有三种情况:

1. 初始化时,由于其上的ngIf或父级ngIf而未呈现ngFor元素

  • 在这种情况下,只要ngIf变为真,就会通过ViewChildren订阅得到通知。

2. 初始化时,无论是否有ngIf在其上或其父级上,都会呈现ngFor元素

  • 在这种情况下,ViewChildren订阅将不会第一时间通知您,但您可以确信它已在ngAfterViewInit钩子中呈现。

3.ngFor数组添加/删除项

  • 在这种情况下,ViewChildren订阅也会通知您。

[Plunker演示] (请查看控制台日志)

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

Translated

您可以这样做(但请查看自己的副作用

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

如果不与changeDetectionStrategy.OnPush一起使用,它就没什么用处了。

然后你可以控制变更检测发生的次数,因此也可以控制函数被调用的次数。

例如:items的数据发生改变时,你可以触发下一个changeDetection,你的函数将正确地指示ngFor已经呈现出真正的变化


1
这个答案更好,因为它不会创建 'span'。 - smnbbrv
1
因为递归,ngFor渲染后会调用函数,从而触发变更检测以再次渲染ngFor并再次调用该函数,如此反复。我不确定为什么它在8次后会停止。 - Ankit Singh
哈哈,@A_Singh说得很对,等待Angular2之王的回答。 - Pardeep Jain
请求提供一个更为完整和最新的示例,以便未来的观众参考。 - DarkNeuron
很高兴我能帮到你 :) - Ankit Singh
显示剩余10条评论

9

我使用了一种轻微的方式来解决其他人抱怨过的问题,它很完美地发挥了作用:

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

然后在你的组件中:

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

这并没有解决这种方法的根本问题,但它是一个极低成本的技巧,让我的应用程序运行顺畅。我希望Angular团队能实现一种更友好的方式,在*ngFor加载其内容后触发一些代码。


3

您可以通过使用*ngFor#last获取最后一个索引,然后通过获取最后一个索引值调用函数并执行您想要的操作。以下是相同的代码 -

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

这是相同问题的示例代码:工作中的 Plunker


这个函数总是执行!就像一个循环! - user5738822
@AlirezaValizade,是的,请阅读@A_singh回答的评论以获得更多澄清。 - Pardeep Jain

2

我也遇到了这个问题。在我的情况下,我正在调用一个Web服务来检索数据,并需要执行JavaScript来初始化由*ngFor生成的每个模板。以下是我的解决方案:

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

setTimout会在DOM更新后执行。这不完全是你要求的,但希望能有所帮助。


它能够工作,但在许多情况下会产生一种不太正式的体验。例如,如果数据在DOM中显示产品,并且您需要使用JavaScript函数进行设计更改(而不是使用javascriptInitializationFunction),则此解决方案将在运行JavaScript函数之前创建产品(数据)的一瞥。我在Angular应用程序中实现Gridify时就有这种经历。 - Peter Højlund Palluth
您也可以在此解决方案中实现已接受的答案。 - Peter Højlund Palluth

1

我遇到了同样的问题,并找到一种方法来解决它。不确定它是否适合你。
前提条件是你正在使用ngFor来呈现传入属性。

背景:我需要调用MDL的upgradeAllRegistered,以使复选框在从后端获取网格数据后变得漂亮。

我在'ngOnChanges'中添加了一个setTimeout。

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>

1
我已经成功地进行了一些黑客攻击,以防止事件被触发(我发现滚动总是会触发这些事件)。
通过添加:[在您的组件中添加]
private globalFlag = -1;

这是我的事件,当循环完成时将会被触发

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

    }
  }

这是循环代码

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

主要思想是如果标志相同,则防止事件被触发。因此,只有当全局标志值与从ngFor传递的当前标志不同时,才会触发它。对于我的糟糕英语表示抱歉,希望您能理解我的想法。

0
可能的解决方案:答案显示在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);
    }

0
我使用了这个技巧,对我的情况很有效。
<div *ngFor="let item of itemList; let i = index;">
     {{ ( (!flag) && ( (i+1) ===  itemList.length) ) ? myFunction() : ''}}
</div>

在我的.js代码中:
let flag : boolean = false;

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接