Angular 2: Обратный вызов, когда ngFor завершена

В Angular 1 я написал специальную директиву ( "готовность к повторителю" ) для использования с 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?

Ответ 2

Для меня работает Angular2 с помощью Typescript.

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

Затем вы можете использовать эту функцию

public ngForCallback() {
  ...
}

Ответ 3

Вместо [ready] используйте [attr.ready], как показано ниже

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

Ответ 4

Я нашел в RC3 принятый ответ не работает. Однако я нашел способ справиться с этим. Для меня мне нужно знать, когда ngFor завершил запуск MDL-компонентаHandler для обновления компонентов.

Сначала вам понадобится директива.

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, он будет работать, когда он будет завершен.

Вам не нужно использовать [attr.upgrade-components], хотя это не родной атрибут, потому что теперь он является директивой bonafide.

Ответ 5

Я пишу демо для этой проблемы. Теория основана на принятом ответе, но этот ответ не является полным, поскольку li должен быть настраиваемым компонентом, который может принимать вход ready.

Я пишу полную демонстрацию для этой проблемы.

Определите новый компонент:

import {Component, Input, OnInit} из '@ angular/core';

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

Ответ 6

Вы можете использовать @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');
  }
}

Оригинальный ответ здесь fooobar.com/questions/418157/...

Ответ 7

Я еще не изучил глубину того, как ngFor создает элементы под капотом. Но из наблюдения я заметил, что часто имеет тенденцию оценивать выражения более одного раза за каждый элемент, который он итерирует.

Это вызывает вызов метода typescript, созданный при проверке переменной ngFor 'last' для получения, иногда срабатывания более одного раза.

Чтобы гарантировать один вызов вашего метода typescript ngFor, когда он правильно завершит итерацию через элементы, вам нужно добавить небольшую защиту от повторной оценки нескольких выражений, которую 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 в файле typescript:

public doSomething=()=> {
    console.log("triggered from the directive parent component when ngFor finishes iterating");
}

Ответ 8

Это директива, которая запускает событие вывода, когда цикл for завершается.

//lastfor-directive.ts

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

declare const $:any;
@Directive({
    selector: '[lastfor]'
})


export class LastDirective {
    @Output() last = new EventEmitter();

    @Input()
        set ready(isReady: boolean) {
            if (isReady) {
            this.last.emit({value : true});
            console.log('true last');
            };
        }
}

Это можно использовать для запуска события вывода в компоненте

//app.component.html

<li *ngFor="let data of Data; let last = last" [ready]="last ? true : false" (last)="onlast('Afterlast')" lastfor value="data.id">{{data.name}}</li>

//app.component.ts Сделать функцию onlast в классе компонентов

export class AppComponent  { 

onlast(str:string){
    console.log('onlast called : '+str);

    }

}