"إذا أراد العامل أن يؤدي عمله بشكل جيد، فعليه أولاً أن يشحذ أدواته." - كونفوشيوس، "مختارات كونفوشيوس. لو لينجونج"
الصفحة الأمامية > برمجة > Angular LAB: لنقم بإنشاء توجيه الرؤية

Angular LAB: لنقم بإنشاء توجيه الرؤية

تم النشر بتاريخ 2024-11-03
تصفح:893

Angular LAB: let

في هذه المقالة سأوضح كيفية إنشاء توجيه زاوي بسيط للغاية يتتبع حالة رؤية العنصر، أو بمعنى آخر، عندما يدخل ويخرج منفذ العرض. آمل أن يكون هذا تمرينًا لطيفًا وربما مفيدًا!

من أجل القيام بذلك، سنستخدم IntersectionObserver JavaScript API المتوفر في المتصفحات الحديثة.

ما نريد تحقيقه

نريد استخدام التوجيه مثل هذا:

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

  • الرؤية هي محدد التوجيه المخصص لدينا
  • إن VisionMonitor هو إدخال اختياري يحدد ما إذا كان سيتم الاستمرار في مراقبة العنصر أم لا (إذا كان خطأ، توقف عن المراقبة عند دخوله إلى إطار العرض)
  • سوف يقوم VisionChange بإعلامنا

سيكون الإخراج بهذا الشكل:

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$ (يمكن ملاحظتها) والتي ستكون بمثابة نظير تفاعلي لربط دورة حياة ngAfterViewInit
  • خاصية تسمى المراقب والتي ستقوم بتخزين 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 عن طريق إرسال عنصر غير محدد.

IntersectionObserver

هذا هو جوهر توجيهاتنا. ستكون طريقة 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 إلى ما إذا كانت رؤية العنصر قد تغيرت: نقارنه بالحالة السابقة (خاصيتنا) وإذا كان مستحقًا، فإننا نصدره. ثم نقوم بتخزين الحالة الجديدة في ممتلكاتنا لوقت لاحق.
  • إذا كانت شاشة الرؤية خاطئة، بمجرد أن يصبح العنصر مرئيًا، نقوم بفصل المراقب: تنتهي مهمته!
  • ثم يتعين علينا بدء المراقب عن طريق تمرير العنصر الخاص بنا، لذلك ننتظر حتى تتم تهيئة العرض الخاص بنا للقيام بذلك.

أخيرًا، دعونا ننفذ الطريقة التي تفصل المراقب، سهل للغاية:

 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