Как правильно обнаружить изменение в вычисляемом свойстве стиля с Angular 2?

Я создал приложение Angular 2, которое содержит компонент, HTML-элемент которого я пытаюсь расположить относительно другого компонента. Чтобы вычислить точные координаты, мне нужно знать ширину и высоту моего собственного элемента, отображаемые браузером.

я использую window.getComputedStyle(this.elementRef.nativeElement) чтобы получить эти свойства. Я заметил, что при визуализации страницы свойства продолжают изменяться до тех пор, пока не завершится рендеринг страницы. Чтобы получать информацию о любых изменениях и корректировать расположение, я проверяю значения в ngAfterViewChecked метод моего компонента.

Тем не менее, похоже ngAfterViewChecked не вызывается, когда рендеринг приводит к новым свойствам вычисленного стиля, так как я обнаружил, что мой хук больше не вызывается, даже если свойства вычисляемого стиля все еще меняются. Я предполагаю, что угловая структура не предназначена для обнаружения таких изменений.

Моей первой попыткой было реализовать ngDoCheck hook, но, похоже, этот не вызывается ни через какое-то время, ни до того, как свойство стиля computed имеет окончательное значение. Я предполагаю, что я еще не до конца понимаю, когда именно этот хук предназначен для вызова.

Я наконец узнал, что, если я реализую setTimeout функция в ngAfterViewChecked, это приводит к тому, что тот же хук будет вызван снова позже, даже если я передам только фиктивную функцию, такую ​​как:

setTimeout(() => {}, 500);

Но, честно говоря, я не понимаю, почему это так. Может кто-нибудь, пожалуйста, объясните мне связь между setTimeout и ngAfterViewChecked крючок?

И хотя это оказывается несколько грязным обходным путем: каков надлежащий "угловой" способ обнаружения и обработки изменения в вычисляемых атрибутах стиля?


Выдержка из кода:

export class MyComponent implements OnInit, AfterViewChecked
{
  private cssWidth: number;
  private cssHeight: number;

  constructor(private elementRef: ElementRef, private renderer: Renderer2)
  {
  }

  ngAfterViewChecked()
  {
    console.log("ngAfterViewChecked");
    this.updateView();
  }

  public updateView()
  {
    const sizeX = parseFloat(window.getComputedStyle(this.elementRef.nativeElement).width) || 0;
    const sizeY = parseFloat(window.getComputedStyle(this.elementRef.nativeElement.height) || 0;

    if (sizeX === this.cssWidth && sizeY === this.cssHeight) {
      // no change
      return;
    }

    console.log("Change detected!");

    // TODO Does not work without this dummy timeout function (else no more changes detected) - why so??
    setTimeout(() => {}, 500);

    [.. doing the positioning here ..]
 }

3 ответа

Решение

Почему не вызывается ngAfterViewChecked, когда рендеринг приводит к новым свойствам вычисляемого стиля?

Инфраструктура Angular не обнаруживает изменения в вычисленном стиле элементов DOM, по крайней мере, когда они вызваны механизмом рендеринга браузера. Следовательно, ngAfterViewChecked крюк не будет вызываться в таких случаях.

Почему это работает при использовании setTimeout?

Как объясняется в ответе Майкла Палмера, setTimeout заставляет Angular осознавать асинхронный процесс, который может вызвать изменение. Следовательно ngAfterViewChecked hook вызывается после того, как была выполнена функция тайм-аута. Angular не пытается проверить, что именно делается в setTimeout функция и является ли это то, что на самом деле влияет на вид. Очевидно, что это будет довольно сложно реализовать и, вероятно, не стоит затраченных усилий. Вместо этого Angular вызывает ngAfterViewChecked перехватывать на всякий случай, если в асинхронном процессе что-то произошло, что влияет на представление.

Что такое Angular для прослушивания вычисленных изменений стиля?

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

Смотрите верхнюю диаграмму здесь, чтобы посмотреть диаграмму жизненного цикла. Если ваш хук в doCheck не работает, AfterViewChecked также не будет работать, так как doCheck инициирует AfterViewChecked.

Каждый раз, когда асинхронный процесс завершается, происходит очередной раунд doCheck. Это вызвало бы AfterViewChecked, и это вызвало бы вашу функцию updateView. SetTimout() является асинхронным процессом. Я подозреваю, что приведенный выше код будет зацикливаться как есть.

Я считаю, что вы хотите, чтобы родительский компонент того, что вы здесь показали, использовал ngAfterViewChecked() и имел этот вызов updateView(). Эта тема рассматривается здесь.

Угловой способ заставить два компонента работать вместе - это иметь родительский компонент, который может передавать изменения между дочерними компонентами, поскольку родительский компонент - это контекст, который может видеть события, происходящие в дочерних компонентах.

Решение, которое я лично использовал бы, было бы EventEmitter, в компоненте, который не показан, в функции ngOnInit(). Это сообщило бы родителю, что это инициализировано. Затем, в родительском компоненте, вызовите функцию, которая происходит с запущенным событием, которое сообщает дочернему компоненту, что вы здесь для updateView.

В ванильном JavaScript вы можете использовать:

MutationObserver

и запускать собственную функцию при изменении DOM.

MutationObserver предоставляет разработчикам способ реагировать на изменения в DOM. Он предназначен для замены событий мутации, определенных в спецификации событий DOM3.

Другие вопросы по тегам