Как определить, когда Angular присоединил элементы к DOM?

#angular

#angular

Вопрос:

Существует компонент:

<my-component></my-component>

этот компонент получает поток чисел в виде @Input

<my-component [data]="numbers$ | async"></my-component>

внутри компонента мы обновляем модель:

 @Component({...})
class MyComponent {

  numbers: number[] = [];

  @Input
  set data(numbers: number[]) {
      this.numbers = numbers;
  }

  constructor() {}

  myCallback() { }
}
  

затем отобразите список чисел следующим образом:

 <ul>
 <li *ngFor="number of numbers">{{number}}</li>
</ul>
  

Как определить, что *ngFor отрисовало все элементы? И как вызвать функцию обратного вызова myCallback после добавления элементов в DOM?

Ответ №1:

Вы можете использовать ViewChildren для получения списка запросов элементов или директив из view DOM. Каждый раз, когда дочерний элемент добавляется, удаляется или перемещается, список запросов будет обновляться, и изменения, наблюдаемые в списке запросов, будут выдавать новое значение.

 import { Component, OnInit, Input, ViewChildren, QueryList } from '@angular/core';

@Component({
  selector: 'my-component',
  templateUrl: './my-component.component.html',
  styleUrls: ['./my-component.component.css']
})
export class MyComponentComponent {

  numbers: number[] = [];
  @ViewChildren('li') elements: QueryList<any>;

  @Input() set data(numbers: number[]) {
    this.numbers = numbers;
  }

  ngAfterViewInit() {
    this.elements.changes.subscribe(li => {
      li.forEach(elm => this.myCallback(elm.nativeElement))
    })
  }

  myCallback(elm) {
    console.log(elm)
  }

}
  

шаблон

 <ul>
    <li #li *ngFor="let n of numbers">
        {{n}}
    </li>
</ul>
  

MyCallback будет регистрировать каждый элемент, но у вас по-прежнему будет доступ ко всем элементам li

демонстрация stackblitz 🚀🚀

Просмотр дочерних элементов

Комментарии:

1. Спасибо за ваш ответ. Я хотел бы подчеркнуть, что мне нужно определить момент, когда angular отрисовал элементы в DOM (после отправки данных). В вашем примере мы просто вызываем myCallback после ввода чисел, на этом шаге <li> элементы еще не отображаются. Чтобы быть более конкретным, я хочу использовать document.querySelector('') для получения доступа к элементам списка.

2. @RomanMahotskyi Я обновил ответ с помощью ViewChildren декоратора 👍

Ответ №2:

Вы можете создать директиву для обнаружения elemntmrender в dom и запустить метод в это время

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

@Directive({
  selector: '[rendered]'
})
export class RenderedDirective {

   @Output() rendered:EventEmitter<any> = new EventEmitter();

   ngAfterViewInit() {
     this.rendered.emit()
   }

}
  

ngAfterViewInit будет вызван после того, как Angular полностью инициализирует a и отобразит li elemnt

MyComponent

 export class MyComponentComponent {

  numbers: number[] = [];

  @Input() set data(numbers: number[]) {
    this.numbers = numbers;
  }

  myCallback(elm) {
    console.log(elm)
  }

}
  

шаблон

 <ul>
    <li #li *ngFor="let n of numbers" (rendered)="myCallback(li)">
        {{n}}
    </li>
</ul>
  

демонстрация stackblitz 🔥🔥

Ответ №3:

это происходит в ngInit

https://angular.io/guide/lifecycle-hooks

ngOnInit()
Инициализируйте директиву / компонент после того, как Angular впервые отобразит свойства, связанные с данными, и установит входные свойства директивы / компонента.

Вызывается один раз, после первого ngOnChanges().

Обновить:

Я, вероятно, неправильно понял ваш вопрос. У вас нет никаких обновлений для ngFor. Каков ваш вариант использования? Возможно, для этого есть разные решения.

Комментарии:

1. корректные данные, которые могут быть асинхронными, могут стать после рендеринга компонента, проверьте консоль в этом примере stackblitz.com/edit/angular-f9v5dj

2. @malbarmawi хорошее решение, но есть обратный вызов для данных, а не для рендеринга. Или это не так?

3. ngOnInit вызывается только один раз после первого ngOnChanges . Мне нужно вызвать myCallback функцию для каждого numbers$.next([1,2,3]) , но сразу после того, как эти элементы будут переданы в DOM.

4. @Fribu-SmartSolutions исправляют, первое решение не сработает, вот почему я использую ViewChildren декоратор