«Если рабочий хочет хорошо выполнять свою работу, он должен сначала заточить свои инструменты» — Конфуций, «Аналитики Конфуция. Лу Лингун»
титульная страница > программирование > Angular LAB: давайте создадим директиву видимости

Angular LAB: давайте создадим директиву видимости

Опубликовано 3 ноября 2024 г.
Просматривать:555

Angular LAB: let

В этой статье я собираюсь проиллюстрировать, как создать очень простую угловую директиву, которая отслеживает состояние видимости элемента, или, другими словами, когда он входит и выходит из него. окно просмотра. Надеюсь, это будет хорошее и, возможно, полезное упражнение!

Для этого мы будем использовать JavaScript API IntersectionObserver, который доступен в современных браузерах.

Чего мы хотим достичь

Мы хотим использовать директиву следующим образом:

I'm being observed! Can you see me yet?

  • видимость — это селектор нашей пользовательской директивы
  • VisibilityMonitor — это необязательный входной параметр, который указывает, следует ли продолжать наблюдение за элементом (если false, прекратить мониторинг, когда он попадает в область просмотра)
  • VisibilityChange уведомит нас

Вывод будет иметь следующую форму:

type VisibilityChange =
  | {
      isVisible: true;
      target: HTMLElement;
    }
  | {
      isVisible: false;
      target: HTMLElement | undefined;
    };

Наличие неопределенной цели будет означать, что элемент был удален из DOM (например, с помощью @if).

Создание Директивы

Наша директива будет просто отслеживать элемент, не меняя структуру DOM: это будет Директива атрибута.

@Directive({
  selector: "[visibility]",
  standalone: true
})
export class VisibilityDirective implements OnInit, OnChanges, AfterViewInit, OnDestroy {
  private element = inject(ElementRef);

  /**
   * Emits after the view is initialized.
   */
  private afterViewInit$ = new Subject();

  /**
   * The IntersectionObserver for this element.
   */
  private observer: IntersectionObserver | undefined;

  /**
   * Last known visibility for this element.
   * Initially, we don't know.
   */
  private isVisible: boolean = undefined;

  /**
   * If false, once the element becomes visible there will be one emission and then nothing.
   * If true, the directive continuously listens to the element and emits whenever it becomes visible or not visible.
   */
  visibilityMonitor = input(false);

  /**
   * Notifies the listener when the element has become visible.
   * If "visibilityMonitor" is true, it continuously notifies the listener when the element goes in/out of view.
   */
  visibilityChange = output();
}

В приведенном выше коде вы видите:

  • ввод и вывод, о которых мы говорили ранее
  • свойство afterViewInit$ (Observable), которое будет действовать как реактивный аналог крючка жизненного цикла ngAfterViewInit
  • свойство под названием Observer, в котором будет храниться IntersectionObserver, отвечающий за мониторинг нашего элемента
  • свойство isVisibile, которое будет хранить последнее состояние видимости, чтобы избежать повторного создания одного и того же состояния дважды подряд

И, естественно, мы добавляем ElementRef, чтобы получить элемент DOM, к которому мы применяем нашу директиву.

Прежде чем писать основной метод, давайте позаботимся о жизненном цикле директивы.

ngOnInit(): void {
  this.reconnectObserver();
}

ngOnChanges(): void {
  this.reconnectObserver();
}

ngAfterViewInit(): void {
  this.afterViewInit$.next();
}

ngOnDestroy(): void {
  // Disconnect and if visibilityMonitor is true, notify the listener
  this.disconnectObserver();
  if (this.visibilityMonitor) {
    this.visibilityChange.emit({
      isVisible: false,
      target: undefined
    });
  }
}

private reconnectObserver(): void {}
private disconnectObserver(): void {}

Теперь вот что происходит:

  • Внутри ngOnInit и ngOnChanges мы перезапускаем наблюдателя. Это сделано для того, чтобы сделать директиву реактивной: если входные данные изменятся, директива начнет вести себя по-другому. Обратите внимание: даже если ngOnChanges также запускается до ngOnInit, нам все равно нужен ngOnInit, потому что ngOnChanges не запускается, если в шаблоне нет входных данных!
  • Когда представление инициализируется, мы активируем Тему, мы доберемся до этого через несколько секунд
  • Мы отключаем нашего наблюдателя при уничтожении директивы, чтобы избежать утечек памяти. Наконец, если разработчик попросил об этом, мы уведомляем, что элемент был удален из DOM, выдавая неопределенный элемент.

ПересечениеОбозреватель

Это суть нашей директивы. Наш метод reconnectObserver начнет наблюдение! Это будет примерно так:

private reconnectObserver(): void {
    // Disconnect an existing observer
    this.disconnectObserver();
    // Sets up a new observer
    this.observer = new IntersectionObserver((entries, observer) => {
      entries.forEach(entry => {
        const { isIntersecting: isVisible, target } = entry;
        const hasChangedVisibility = isVisible !== this.isVisible;
        const shouldEmit = isVisible || (!isVisible && this.visibilityMonitor);
        if (hasChangedVisibility && shouldEmit) {
          this.visibilityChange.emit({
            isVisible,
            target: target as HTMLElement
          });
          this.isVisible = isVisible;
        }
        // If visilibilyMonitor is false, once the element is visible we stop.
        if (isVisible && !this.visibilityMonitor) {
          observer.disconnect();
        }
      });
    });
    // Start observing once the view is initialized
    this.afterViewInit$.subscribe(() => {
        this.observer?.observe(this.element.nativeElement);
    });
  }

Поверьте, это не так сложно, как кажется! Вот механизм:

  • Сначала мы отключаем наблюдателя, если он уже запущен
  • Мы создаем IntersectionObserver и определяем его поведение. Записи будут содержать отслеживаемые элементы, поэтому они будут содержать наш элемент. Свойство isIntersecting укажет, изменилась ли видимость элемента: мы сравниваем его с предыдущим состоянием (нашим свойством) и, если это необходимо, выдаем сообщение. Затем мы сохраняем новое состояние в нашей собственности на будущее.
  • Если видимостьMonitor имеет значение false, как только элемент становится видимым, мы отключаем наблюдателя: его работа выполнена!
  • Затем нам нужно запустить наблюдателя, передав наш элемент, поэтому мы ждем, пока наше представление будет инициализировано, чтобы сделать это.

Наконец, давайте реализуем метод, который отключит наблюдателя, это очень просто:

 private disconnectObserver(): void {
    if (this.observer) {
      this.observer.disconnect();
      this.observer = undefined;
    }
  }

Окончательный код

Вот полная директива. Это было всего лишь упражнение, поэтому вы можете изменить его по своему усмотрению!

type VisibilityChange =
  | {
      isVisible: true;
      target: HTMLElement;
    }
  | {
      isVisible: false;
      target: HTMLElement | undefined;
    };

@Directive({
  selector: "[visibility]",
  standalone: true
})
export class VisibilityDirective
  implements OnChanges, OnInit, AfterViewInit, OnDestroy {
  private element = inject(ElementRef);

  /**
   * Emits after the view is initialized.
   */
  private afterViewInit$ = new Subject();

  /**
   * The IntersectionObserver for this element.
   */
  private observer: IntersectionObserver | undefined;

  /**
   * Last known visibility for this element.
   * Initially, we don't know.
   */
  private isVisible: boolean = undefined;

  /**
   * If false, once the element becomes visible there will be one emission and then nothing.
   * If true, the directive continuously listens to the element and emits whenever it becomes visible or not visible.
   */
  visibilityMonitor = input(false);

  /**
   * Notifies the listener when the element has become visible.
   * If "visibilityMonitor" is true, it continuously notifies the listener when the element goes in/out of view.
   */
  visibilityChange = output();

  ngOnInit(): void {
    this.reconnectObserver();
  }

  ngOnChanges(): void {
    this.reconnectObserver();
  }

  ngAfterViewInit(): void {
    this.afterViewInit$.next(true);
  }

  ngOnDestroy(): void {
    // Disconnect and if visibilityMonitor is true, notify the listener
    this.disconnectObserver();
    if (this.visibilityMonitor) {
      this.visibilityChange.emit({
        isVisible: false,
        target: undefined
      });
    }
  }

  private reconnectObserver(): void {
    // Disconnect an existing observer
    this.disconnectObserver();
    // Sets up a new observer
    this.observer = new IntersectionObserver((entries, observer) => {
      entries.forEach(entry => {
        const { isIntersecting: isVisible, target } = entry;
        const hasChangedVisibility = isVisible !== this.isVisible;
        const shouldEmit = isVisible || (!isVisible && this.visibilityMonitor);
        if (hasChangedVisibility && shouldEmit) {
          this.visibilityChange.emit({
            isVisible,
            target: target as HTMLElement
          });
          this.isVisible = isVisible;
        }
        // If visilibilyMonitor is false, once the element is visible we stop.
        if (isVisible && !this.visibilityMonitor) {
          observer.disconnect();
        }
      });
    });
    // Start observing once the view is initialized
    this.afterViewInit$.subscribe(() => {
        this.observer?.observe(this.element.nativeElement);
    });
  }

  private disconnectObserver(): void {
    if (this.observer) {
      this.observer.disconnect();
      this.observer = undefined;
    }
  }
}
Заявление о выпуске Эта статья воспроизведена по адресу: https://dev.to/this-is-angular/angular-lab-lets-create-a-visibility-directive-5dpp?1 Если есть какие-либо нарушения, пожалуйста, свяжитесь с [email protected] удалить его
Последний учебник Более>

Изучайте китайский

Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.

Copyright© 2022 湘ICP备2022001581号-3