Angular 2: обратный вызов после завершения ngFor

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


person Tobias Punke    schedule 05.03.2016    source источник
comment
Мне сложно представить, когда это будет полезно. почему ты хочешь сделать это? Может быть, есть другой способ решить вашу настоящую проблему.   -  person Douglas    schedule 05.03.2016
comment
Это может помочь, angular.io/docs/ts/latest/ api / common / NgFor-directive.html - NgFor предоставляет несколько экспортируемых значений, которым можно присвоить псевдонимы локальных переменных: одно из них является последним. Но я согласен с тем, что это звучит как неправильное решение.   -  person Dave Bush    schedule 05.03.2016
comment
Причина в настраиваемой директиве для раскрывающегося списка с использованием Sematic-UI. Мне нужно вызвать метод из семантического API, чтобы сделать ввод раскрывающимся списком, но это нужно сделать после того, как ngFor прошел цикл по всем элементам.   -  person Tobias Punke    schedule 06.03.2016
comment
Привет, Тобиас, ты как-то решил эту проблему? У нас такие же проблемы, когда мне нужно инициализировать полосы прокрутки после появления новых элементов в списке ngFor.   -  person prespic    schedule 01.04.2016
comment
Такая же проблема здесь ...   -  person Marek    schedule 15.01.2018


Ответы (8)


Вы можете использовать что-то вроде этого (ngFor локальных переменных) :

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

Затем вы можете Перехватить изменения входных свойств с сеттером

  @Input()
  set ready(isReady: boolean) {
    if (isReady) someCallbackMethod();
  }
person Sasxa    schedule 06.03.2016
comment
По крайней мере, это не будет работать с элементом li, а будет работать с пользовательским компонентом, который будет вложен внутрь. - person Tobias Punke; 07.03.2016
comment
я думаю, что код должен быть таким: `* ngFor = # item of Items;` не так ли? btw код выдает ошибку ready is not know native property blabla... - person Pardeep Jain; 07.05.2016
comment
необходимо добавить новый компонент для выполнения этой работы. см. эту демонстрацию: github.com/xmeng1/ngfor-finish-callback - person Xin Meng; 12.05.2017
comment
Работает с этим: <li *ngFor="item in Items; last = last" [attr.ready]="last ? false : true"> - person claudchan; 21.06.2017
comment
это не сработало, как говорят все остальные, ошибка синтаксического анализа шаблона для использования в ‹li› - person Mohammad Mirzaeyan; 07.06.2018
comment
Здесь есть более полный ответ: stackoverflow.com/a/37088348/1574059 - person danguilherme; 12.06.2018
comment
ошибка синтаксического анализа шаблона, м использую его при выборе - опция - person Sunil Garg; 18.04.2019
comment
прекрасное решение :) - person Dmitry Sobolevsky; 10.01.2020

Для этой цели можно использовать @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

person Abhijit Jagtap    schedule 28.02.2018
comment
Работает отлично. Спасибо! - person Devon Sams; 01.03.2018
comment
Это должен быть принятый ответ, потому что он правильно обрабатывает вопрос о состоянии представления, используя то, что предоставил Angular. Все остальное просто взлом. - person Reactgular; 29.07.2018
comment
Замечательное решение! - person Max; 29.08.2018
comment
лучшее решение этой проблемы. - person Pavel B.; 19.12.2018

У меня работает в Angular2 с использованием Typescript.

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

Тогда вы можете справиться с помощью этой функции

public ngForCallback() {
  ...
}
person FACode    schedule 07.01.2017
comment
Для меня это выполняется миллион раз - person Alex; 28.02.2017
comment
Привет, Алекс, ты прав, эта функция будет выполняться Items.lenght раз. Это не очень хороший подход, он подходит для небольших и быстрых вещей. - person FACode; 28.02.2017
comment
Нет, это не будет выполнено items.length раз. Это будет продолжаться вечно, лол - person Marco Noronha; 02.05.2017
comment
Не используйте это решение, если для вашего ChangeDetectionStrategy не установлено значение OnPush. - person DarkNeuron; 12.06.2017
comment
@MarcoNoronha Я тестирую это, и это не длится вечно - person FACode; 12.06.2017
comment
@DarkNeuron, тогда какое лучшее решение для этого? - person FACode; 12.06.2017
comment
Вы можете использовать канал, так как он не запускает обнаружение изменений, или установить стратегию OnPush. Труба будет выглядеть примерно так: <ele *ngFor="let item of items; let i = last">{{ i | lastElementTrigger:optionalParameters }}</ele> - person DarkNeuron; 12.06.2017
comment
Использование фильтра по диапазону не было бы лучшей альтернативой? - person Rodolfo Jorge Nemer Nogueira; 15.03.2019

Решение довольно тривиальное. Если вам нужно знать, когда 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(); }

И не забывайте использовать ChangeDetectorRef, чтобы избежать ExpressionChangedAfterItHasBeenCheckedError.

4. Используйте значение ngFor last.

Объявить переменную last на ngFor. Используйте его внутри li для запуска метода, когда этот элемент является последним:

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

  • Используйте свойство флага компонента contentPrinted, чтобы запускать onContentPrinted() только один раз.
  • Используйте ng-container, чтобы не влиять на макет.
person vulp    schedule 12.07.2018
comment
«Тривиально», но для объяснения требуется 4 шага - person Matthieu Charbonnier; 27.09.2019

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

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

Я обнаружил, что в 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 это будет выполняться по завершении.

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

person Warren Schilpzand    schedule 27.06.2016

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

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

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

импортировать {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.

person Xin Meng    schedule 12.05.2017

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

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

Чтобы гарантировать один вызов вашего метода машинописного текста с помощью 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» не имеет здесь скобок «()», поскольку мы просто передаем ссылку на метод машинописного текста, а не вызываем его здесь.

И, наконец, вот как выглядит метод doSomething в вашем машинописном файле:

public doSomething=()=> {
    console.log("triggered from the directive's parent component when ngFor finishes iterating");
}
person H7O    schedule 24.06.2017