35

在 Angular 1 中,我编写了一个自定义指令(“repeater-ready”),用于ng-repeat在迭代完成时调用回调方法:

if ($scope.$last === true)
{
    $timeout(() =>
    {
        $scope.$parent.$parent.$eval(someCallbackMethod);
    });
}

标记中的用法:

<li ng-repeat="item in vm.Items track by item.Identifier"
    repeater-ready="vm.CallThisWhenNgRepeatHasFinished()">

如何ngFor在 Angular 2 中实现类似的功能?

4

8 回答 8

64

您可以为此目的使用@ViewChildren

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

  ngForRendred() {
    console.log('NgFor is Rendered');
  }
}

原始答案在这里 https://stackoverflow.com/a/37088348/5700401

于 2018-02-28T08:28:47.487 回答
22

你可以使用这样的东西(ngFor 局部变量):

<li *ngFor="#item in Items; #last = last" [ready]="last ? false : true">

然后您可以使用 setter 拦截输入属性更改

  @Input()
  set ready(isReady: boolean) {
    if (isReady) someCallbackMethod();
  }
于 2016-03-06T12:04:09.627 回答
6

对我来说,使用 Typescript 在 Angular2 中工作。

<li *ngFor="let item in Items; let last = last">
  ...
  <span *ngIf="last">{{ngForCallback()}}</span>
</li>

然后您可以使用此功能进行处理

public ngForCallback() {
  ...
}
于 2017-01-07T14:31:34.920 回答
5

解决方案非常简单。如果您需要知道何时ngFor完成将所有 DOM 元素打印到浏览器窗口,请执行以下操作:

1.添加占位符

为正在打印的内容添加一个占位符:

<div *ngIf="!contentPrinted">Rendering content...</div>

2.添加容器

为内容创建一个容器display: none。打印完所有项目后,执行display: block. contentPrinted是一个组件标志属性,默认为false

<ul [class.visible]="contentPrinted"> ...items </ul>

3.创建回调方法

添加onContentPrinted()到组件,该组件在ngFor完成后会自行禁用:

onContentPrinted() { this.contentPrinted = true; this.changeDetector.detectChanges(); }

并且不要忘记使用ChangeDetectorRefto Avoid ExpressionChangedAfterItHasBeenCheckedError

4.使用ngForlast

在 上声明last变量ngForli当此项为最后一项时,在内部使用它来运行方法:

<li *ngFor="let item of items; let last = last"> ... <ng-container *ngIf="last && !contentPrinted"> {{ onContentPrinted() }} </ng-container> <li>

  • 使用contentPrinted组件标志属性onContentPrinted() 只运行一次
  • 用于ng-container不影响布局。
于 2018-07-12T13:49:52.077 回答
4

代替 [ready],使用 [attr.ready] 如下

<li *ngFor="#item in Items; #last = last" [attr.ready]="last ? false : true">
于 2016-06-10T13:06:55.727 回答
3

我在 RC3 中发现接受的答案不起作用。但是,我找到了解决此问题的方法。对我来说,我需要知道 ngFor 何时完成运行 MDL componentHandler 来升级组件。

首先你需要一个指令。

upgradeComponents.directive.ts

import { Directive, ElementRef, Input } from '@angular/core';

declare var componentHandler : any;

@Directive({ selector: '[upgrade-components]' })
export class UpgradeComponentsDirective{

    @Input('upgrade-components')
    set upgradeComponents(upgrade : boolean){
        if(upgrade) componentHandler.upgradeAllRegistered();
    }
}

接下来将其导入您的组件并将其添加到指令中

import {UpgradeComponentsDirective} from './upgradeComponents.directive';

@Component({
    templateUrl: 'templates/mytemplate.html',
    directives: [UpgradeComponentsDirective]
})

现在在 HTML 中将“upgrade-components”属性设置为 true。

 <div *ngFor='let item of items;let last=last' [upgrade-components]="last ? true : false">

当此属性设置为 true 时,它​​将运行 @Input() 声明下的方法。在我的例子中,它运行 componentHandler.upgradeAllRegistered()。但是,它可以用于您选择的任何内容。通过绑定到 ngFor 语句的“last”属性,这将在完成时运行。

您不需要使用 [attr.upgrade-components] 即使这不是本机属性,因为它现在是一个真正的指令。

于 2016-06-27T04:54:01.670 回答
1

我为这个问题写了一个演示。该理论基于公认的答案,但这个答案并不完整,因为它li应该是一个可以接受ready输入的自定义组件。

我为这个问题写了一个完整的演示。

定义一个新组件:

从'@angular/core'导入{组件,输入,OnInit};

@Component({
  selector: 'app-li-ready',
  templateUrl: './li-ready.component.html',
  styleUrls: ['./li-ready.component.css']
})
export class LiReadyComponent implements OnInit {

  items: string[] = [];

  @Input() item;
  constructor() { }

  ngOnInit(): void {
    console.log('LiReadyComponent');
  }

  @Input()
  set ready(isReady: boolean) {
    if (isReady) {
      console.log('===isReady!');
    }
  }
}

模板

{{item}}

在应用程序组件中的使用

<app-li-ready *ngFor="let item of items;  let last1 = last;" [ready]="last1" [item]="item"></app-li-ready>

您将看到控制台中的日志将打印所有项目字符串,然后打印 isReady。

于 2017-05-12T10:33:37.370 回答
-1

我还没有深入研究 ngFor 如何在后台渲染元素。但从观察来看,我注意到它通常倾向于对每个迭代的项目多次评估表达式。

这会导致在检查 ngFor 'last' 变量时进行的任何打字稿方法调用有时会被触发多次。

为了保证 ngFor 在正确完成对项目的迭代时对您的 typescript 方法进行一次调用,您需要添加一个小保护措施,以防止 ngFor 在后台进行的多重表达式重新评估。

这是一种方法(通过指令),希望它有所帮助:

指令代码

import { Directive, OnDestroy, Input, AfterViewInit } from '@angular/core';

@Directive({
  selector: '[callback]'
})
export class CallbackDirective implements AfterViewInit, OnDestroy {
  is_init:boolean = false;
  called:boolean = false;
  @Input('callback') callback:()=>any;

  constructor() { }

  ngAfterViewInit():void{
    this.is_init = true;
  }

  ngOnDestroy():void {
    this.is_init = false;
    this.called = false;
  }

  @Input('callback-condition') 
  set condition(value: any) {
      if (value==false || this.called) return;

      // in case callback-condition is set prior ngAfterViewInit is called
      if (!this.is_init) {
        setTimeout(()=>this.condition = value, 50);
        return;
      }

      if (this.callback) {
        this.callback();
        this.called = true;
      }
      else console.error("callback is null");

  }

}

在你的模块中声明了上述指令之后(假设你知道怎么做,如果不知道,问我希望用代码片段更新它),这里是如何使用 ngFor 的指令:

<li *ngFor="let item of some_list;let last = last;" [callback]="doSomething" [callback-condition]="last">{{item}}</li>

'doSomething' 是 TypeScript 文件中的方法名称,当 ngFor 完成对项目的迭代时要调用它。

注意: 'doSomething' 在这里没有方括号 '()',因为我们只是传递了对 typescript 方法的引用,而实际上并没有在这里调用它。

最后是你的打字稿文件中“doSomething”方法的样子:

public doSomething=()=> {
    console.log("triggered from the directive's parent component when ngFor finishes iterating");
}
于 2017-06-24T03:03:38.410 回答