Angular 2:ngFor 完成时的回调

Posted

技术标签:

【中文标题】Angular 2:ngFor 完成时的回调【英文标题】:Angular 2: Callback when ngFor has finished 【发布时间】:2016-06-19 13:24:14 【问题描述】:

在 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()">

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

【问题讨论】:

我很难想象这会在什么时候有用。你为什么要这样做?也许还有另一种方法可以解决您的实际问题。 这可能会有所帮助,angular.io/docs/ts/latest/api/common/NgFor-directive.html -- “NgFor 提供了几个可以别名为局部变量的导出值:”其中一个是“last”。但我同意这听起来像是错误的解决方案。 原因是使用 Sematic-UI 的下拉菜单的自定义指令。我必须从语义 API 调用一个方法,使输入成为下拉菜单,但这必须在 ngFor 遍历所有元素之后完成。 你好,Tobias,你解决了这个问题了吗?我们有同样的问题,当我需要在 ngFor 列表中出现新项目后初始化滚动条时。 execute a function when *ngFor finished in angular 2的可能重复 【参考方案1】:

您可以为此目的使用@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://***.com/a/37088348/5700401

【讨论】:

效果很好。谢谢! 这应该是公认的答案,因为它使用 Angular 提供的正确处理视图状态的问题。其他一切都只是一个技巧。 惊人的解决方案! 这个问题的最佳解决方案。【参考方案2】:

你可以使用这样的东西 (ngFor local variables):

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

那你可以Intercept input property changes with a setter

  @Input()
  set ready(isReady: boolean) 
    if (isReady) someCallbackMethod();
  

【讨论】:

至少,这不适用于li 元素,但适用于嵌套在其中的自定义组件。 我认为代码应该是这样的` *ngFor="#item of Items;` 不是吗?顺便说一句,代码会抛出ready is not know native property blabla...的错误 需要添加一个新组件来完成这项工作。看这个演示:github.com/xmeng1/ngfor-finish-callback 与此一起使用:&lt;li *ngFor="item in Items; last = last" [attr.ready]="last ? false : true"&gt; 这并不像所有其他人所说的那样工作,模板解析错误在 中使用【参考方案3】:

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

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

那么你就可以使用这个函数来处理了

public ngForCallback() 
  ...

【讨论】:

对我来说,这会被执行一百万次 嗨,Alex,你是对的,这个函数会被 Items.lenght 次执行。这不是一个很好的方法,适用于小而快的事情。 不,它不会被执行 items.length 次。它将永远持续下去,哈哈 请不要使用此解决方案,除非您的 ChangeDetectionStrategy 设置为 OnPush。 @MarcoNoronha 我测试了它,但它不会永远存在【参考方案4】:

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

1。添加占位符

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

&lt;div *ngIf="!contentPrinted"&gt;Rendering content...&lt;/div&gt;

2。添加容器

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

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

3。创建回调方法

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

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

并且不要忘记使用ChangeDetectorRef 来避免ExpressionChangedAfterItHasBeenCheckedError

4。使用 ngFor last

ngFor 上声明last 变量。当该项为最后一项时,在li 中使用它来运行方法:

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

使用contentPrinted 组件标志属性运行onContentPrinted()只运行一次。 使用ng-container 不会影响布局。

【讨论】:

'琐碎',但需要4个步骤来解释【参考方案5】:

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

【讨论】:

【参考方案6】:

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

首先你需要一个指令。

升级组件.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],即使这不是本机属性,因为它现在是一个真正的指令。

【讨论】:

【参考方案7】:

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

我为此问题写了a complete demo。

定义一个新组件:

从 '@angular/core' 导入 Component, Input, 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>

你会看到控制台中的日志会打印所有的item字符串,然后打印isReady。

【讨论】:

【参考方案8】:

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

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

为保证 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");

  


在你的模块中声明了上面的指令之后(假设你知道怎么做,如果不知道,问我希望用代码 sn-p 更新它),这里是如何使用 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");

【讨论】:

以上是关于Angular 2:ngFor 完成时的回调的主要内容,如果未能解决你的问题,请参考以下文章

Angular2 内置指令 NgFor 和 NgIf 详解

Angular 2 嵌套 *ngFor

从 Angular 2 理解 *ngFor

Angular 2 - NgFor 中与 NgModel 的 2 路绑定

Angular 2 - NgFor 使用数字而不是集合

在 ngFor 中计数 - Angular 2