
الجانب العكسي للاشتراك الملحوظ
لدى Observables طريقة اشتراك تسمى مع وظيفة رد اتصال للحصول على القيم المرسلة ( تنبعث منها ) إلى الملاحظة. في الزاوي ، يتم استخدامه في المكونات / التوجيهات ، وخاصة في وحدة التوجيه ، NgRx و HTTP.
إذا اشتركنا في دفق ، فسيظل مفتوحًا وسيتم الاتصال به عندما يتم نقل القيم من أي جزء من التطبيق إليها ، حتى يتم إغلاقه عن طريق الاتصال بإلغاء الاشتراك .
@Component({...}) export class AppComponent implements OnInit { subscription: Subscription ngOnInit () { const observable = Rx.Observable.interval(1000); this.subscription = observable.subscribe(x => console.log(x)); } }
في هذا التطبيق ، نستخدم الفاصل الزمني لإرسال القيم كل ثانية. نحن نشترك فيه للحصول على القيمة المرسلة ، وتقوم وظيفة رد الاتصال الخاصة بنا بكتابة النتيجة على وحدة تحكم المتصفح.
الآن ، إذا تم إتلاف AppComponent ، على سبيل المثال ، بعد الخروج من المكون أو استخدام طريقة الدمار () ، سنظل نرى سجل وحدة التحكم في المستعرض. هذا لأنه على الرغم من تدمير AppComponent ، لم يتم إلغاء الاشتراك.
إذا لم يتم إغلاق الاشتراك ، فسيتم استدعاء وظيفة رد الاتصال بشكل مستمر ، مما سيؤدي إلى حدوث مشكلات خطيرة في تسرب الذاكرة والأداء. لتجنب التسريبات ، من الضروري "إلغاء الاشتراك" من الملاحظة في كل مرة.
1. باستخدام طريقة إلغاء الاشتراك
أي اشتراك لديه وظيفة إلغاء الاشتراك () لتحرير الموارد وإلغاء تنفيذ الملاحظة. لمنع حدوث تسرب للذاكرة ، يجب إلغاء الاشتراك باستخدام طريقة إلغاء الاشتراك في الملاحظة.
في الزاوي ، تحتاج إلى إلغاء الاشتراك من Observable عند إتلاف أحد المكونات. لحسن الحظ ، تحتوي Angular على خطاف ngOnDestroy يسمى قبل إتلاف المكون ، والذي يسمح للمطوّرين بضمان تنظيف الذاكرة ، لتجنب تجميد الاشتراكات ، والمنافذ المفتوحة ، و "اللقطات الموجودة في القدم".
@Component({…}) export class AppComponent implements OnInit, OnDestroy { subscription: Subscription ngOnInit () { const observable = Rx.Observable.interval(1000); this.subscription = observable.subscribe(x => console.log(x)); } ngOnDestroy() { this.subscription.unsubscribe() } }
أضفنا ngOnDestroy إلى AppComponent الخاص بنا ودعانا إلى طريقة إلغاء الاشتراك في Observable this.subscription . عندما يتم إتلاف AppComponent (من خلال النقر فوق الارتباط وطريقة التدمير () ، إلخ) ، لن يتم تجميد الاشتراك ، وسيتم إيقاف الفاصل ، ولن يكون هناك المزيد من سجلات وحدة التحكم في المستعرض.
ولكن ماذا لو كان لدينا عدة اشتراكات؟
@Component({…}) export class AppComponent implements OnInit, OnDestroy { subscription1$: Subscription; subscription2$: Subscription; ngOnInit () { const observable1$ = Rx.Observable.interval(1000); const observable2$ = Rx.Observable.interval(400); this.subscription1$ = observable.subscribe(x => console.log("From interval 1000" x)); this.subscription2$ = observable.subscribe(x => console.log("From interval 400" x)); } ngOnDestroy() { this.subscription1$.unsubscribe(); this.subscription2$.unsubscribe(); } }
هناك نوعان من الاشتراكات في AppComponent وكلاهما غير مشترك في ربط ngOnDestroy ، مما يمنع حدوث تسرب للذاكرة.
يمكنك أيضًا جمع جميع الاشتراكات في صفيف وإلغاء الاشتراك منها في حلقة:
@Component({…}) export class AppComponent implements OnInit, OnDestroy { subscription1$: Subscription; subscription2$: Subscription; subscriptions: Subscription[] = []; ngOnInit () { const observable1$ = Rx.Observable.interval(1000); const observable2$ = Rx.Observable.interval(400); this.subscription1$ = observable.subscribe(x => console.log("From interval 1000" x)); this.subscription2$ = observable.subscribe(x => console.log("From interval 400" x)); this.subscriptions.push(this.subscription1$); this.subscriptions.push(this.subscription2$); } ngOnDestroy() { this.subscriptions.forEach((subscription) => subscription.unsubscribe()); } }
طريقة الاشتراك بإرجاع كائن RxJS من نوع اشتراك. إنه مورد لمرة واحدة. يمكن تجميع الاشتراكات باستخدام طريقة الإضافة ، والتي ستلحق الاشتراك التابع بالأخرى الحالية. عند إلغاء الاشتراك ، يتم إلغاء اشتراك جميع أطفاله أيضًا. دعنا نحاول إعادة كتابة AppComponent لدينا:
@Component({…}) export class AppComponent implements OnInit, OnDestroy { subscription: Subscription; ngOnInit () { const observable1$ = Rx.Observable.interval(1000); const observable2$ = Rx.Observable.interval(400); const subscription1$ = observable.subscribe(x => console.log("From interval 1000" x)); const subscription2$ = observable.subscribe(x => console.log("From interval 400" x)); this.subscription.add(subscription1$); this.subscription.add(subscription2$); } ngOnDestroy() { this.subscription.unsubscribe() } }
لذلك سوف نكتب this.subscripton1 $ و this.subscripton2 $ في وقت تدمير المكون.
2. استخدام المتزامن | أنبوب
اشتراك مزامنة توجيه الإخراج إلى الملاحظة أو الوعد وإرجاع القيمة الأخيرة التي تم تمريرها. عندما يتم تقديم قيمة جديدة ، يتحقق التزامن غير المباشر للمكون من تتبع التغيير. في حالة إتلاف المكون ، يتم إلغاء اشتراك مزامنة الأنابيب تلقائيًا.
@Component({ ..., template: ` <div> Interval: {{observable$ | async}} </div> ` }) export class AppComponent implements OnInit { observable$; ngOnInit () { this.observable$ = Rx.Observable.interval(1000); } }
عند التهيئة ، سيقوم AppComponent بإنشاء "ملاحظة" من طريقة الفاصل. في نمط $ يمكن ملاحظته ، يتم تمرير المتزامن . يشترك في الملاحظة $ ويعرض قيمته في DOM. سيقوم أيضًا بإلغاء الاشتراك عند إتلاف AppComponent. يحتوي توجيه الإخراج غير المتزامن في فئته على ربط ngOnDestroy ، لذلك سيتم استدعاؤه عند إتلاف طريقة العرض الخاصة به.
يعتبر استخدام الأنابيب غير المتزامن مناسبًا للغاية ، لأنه سيشترك هو نفسه في إلغاء الاشتراك وإلغاء الاشتراك منه. والآن لا يمكننا القلق إذا نسينا إلغاء الاشتراك في ngOnDestroy .
3. باستخدام RxJS تأخذ * البيانات
يحتوي RxJS على عوامل تشغيل مفيدة يمكنك استخدامها بطريقة تعريفية لإلغاء الاشتراك في مشروعنا Angular. واحد منهم هو مشغلي عائلة * take **:
- خذ (ن)
- takeUntil (المخطر)
- takeWhile (المسند)
خذ (ن)
هذا المشغل ينبعث منه الاشتراك الأصلي المحدد عدد المرات وينتهي. في أغلب الأحيان ، يتم تمرير واحد (1) للاشتراك والخروج.
يكون هذا المشغل مفيدًا إذا أردنا أن يقوم Observable بتمرير قيمة مرة واحدة ، ثم إلغاء الاشتراك من الدفق:
@Component({...}) export class AppComponent implements OnInit { subscription$; ngOnInit () { const observable$ = Rx.Observable.interval(1000); this.subscription$ = observable$.pipe(take(1)). subscribe(x => console.log(x)); } }
سوف الاشتراك $ إلغاء الاشتراك عندما يمر الفاصل الزمني القيمة الأولى.
يرجى ملاحظة : حتى إذا تم إتلاف AppComponent ، فإن الاشتراك بالدولار لن يلغي الاشتراك حتى يتجاوز الفاصل الزمني القيمة. لذلك ، من الأفضل التأكد من إلغاء اشتراك كل شيء في الخطاف ngOnDestroy :
@Component({…}) export class AppComponent implements OnInit, OnDestroy { subscription$; ngOnInit () { var observable$ = Rx.Observable.interval(1000); this.subscription$ = observable$.pipe(take(1)).subscribe(x => console.log(x)); } ngOnDestroy() { this.subscription$.unsubscribe(); } }
takeUntil (المخطر)
هذا البيان ينبعث القيم من الملاحظة الأصلية حتى يقوم المخطر بإرسال رسالة إتمام.
@Component({…}) export class AppComponent implements OnInit, OnDestroy { notifier = new Subject(); ngOnInit () { const observable$ = Rx.Observable.interval(1000); observable$.pipe(takeUntil(this.notifier)).subscribe(x => console.log(x)); } ngOnDestroy() { this.notifier.next(); this.notifier.complete(); } }
لدينا موضوع إضافي للإشعارات ، والذي سيرسل أمرًا لإلغاء الاشتراك في هذا . الاشتراك. نحن الأنابيب يمكن ملاحظتها في takeUntil حتى يتم توقيعنا. سوف TakeUntil تنبعث منها رسائل الفاصل حتى يخطر المخطر الاشتراك ملحوظ $ . من الأفضل وضع المخطر في خطاف ngOnDestroy .
takeWhile (المسند)
هذا البيان سوف ينبعث منها قيم يمكن ملاحظتها طالما أنها تطابق الشرط المسند.
@Component({...}) export class AppComponent implements OnInit { ngOnInit () { const observable$ = Rx.Observable.interval(1000); observable$.pipe(takeWhile(value => value < 10)).subscribe(x => console.log(x)); } }
نحن نلاحظ أن $ يمكن ملاحظته مع مشغل takeWhile ، والذي سيرسل قيمًا طالما كانت أقل من 10. إذا جاءت قيمة أكبر من أو تساوي 10 ، فسوف يقوم المشغل بإلغاء الاشتراك.
من المهم أن نفهم أن الاشتراك الذي يمكن ملاحظته بالدولار سيكون مفتوحًا حتى يتم إعطاء الفاصل الزمني 10. لذلك ، من أجل الأمان ، نضيف رابط ngOnDestroy لإلغاء الاشتراك من $ يمكن ملاحظته عند إتلاف المكون.
@Component({…}) export class AppComponent implements OnInit, OnDestroy { subscription$; ngOnInit () { var observable$ = Rx.Observable.interval(1000); this.subscription$ = observable$.pipe(takeWhile(value => value < 10)).subscribe(x => console.log(x)); } ngOnDestroy() { this.subscription$.unsubscribe(); } }
4. استخدام عامل التشغيل RxJS الأول
تشبه هذه العبارة الجمع (1) و takeWhile.
إذا تم استدعاؤها بدون معلمة ، فستكون القيمة الأولى التي يتم إرسالها "ملاحظة" وتنتهي. إذا تم استدعاؤها بوظيفة دالة التقييم ، فحينئذٍ ، تنبعث منها القيمة الأولى للملاحظة الأصلية ، والتي تتوافق مع حالة الوظيفة الأصلية ، وتنتهي.
@Component({...}) export class AppComponent implements OnInit { observable$; ngOnInit () { this.observable = Rx.Observable.interval(1000); this.observable$.pipe(first()).subscribe(x => console.log(x)); } }
سينتهي $ ملاحظ إذا تجاوز الفاصل الزمني قيمته الأولى. هذا يعني أنه في وحدة التحكم سنرى رسالة سجل واحدة فقط.
@Component({...}) export class AppComponent implements OnInit { observable$; ngOnInit () { this.observable$ = Rx.Observable.interval(1000); this.observable$.pipe(first(val => val === 10)).subscribe(x => console.log(x)); } }
هنا أولاً لن ينبعث منها القيم حتى يمر الفاصل الزمني 10 ، ثم يكمل الملاحظة $ . في وحدة التحكم ، سنرى رسالة واحدة فقط.
في المثال الأول ، إذا تم إتلاف AppComponent قبل أن تحصل الأولى على القيمة من $ ملاحظ ، فسيظل الاشتراك مفتوحًا حتى يتم تلقي الرسالة الأولى.
أيضًا ، في المثال الثاني ، إذا تم تدمير AppComponent قبل إرجاع الفاصل الزمني قيمة مطابقة لحالة المشغل ، فسيظل الاشتراك مفتوحًا حتى يعود الفاصل الزمني 10. ومن أجل ضمان الأمان ، يجب علينا إلغاء الاشتراكات في ربط ngOnDestroy .
5. باستخدام Decorator لأتمتة إلغاء الاشتراك
كلنا بشر ، نميل إلى النسيان. تعتمد معظم الطرق السابقة على ربط ngOnDestroy للتأكد من مسح الاشتراك قبل إتلاف المكون. ولكن يمكننا أن ننسى تسجيلهم في ngOnDestroy - ربما بسبب الموعد النهائي ، أو العميل العصبي الذي يعرف مكان إقامتك ...
في هذه الحالة ، يمكننا استخدام Decorators في مشاريعنا Angular لإلغاء الاشتراك تلقائيًا من جميع الاشتراكات في المكون.
فيما يلي مثال على هذا التطبيق المفيد:
function AutoUnsub() { return function(constructor) { const orig = constructor.prototype.ngOnDestroy; constructor.prototype.ngOnDestroy = function() { for(let prop in this) { const property = this[prop]; if(typeof property.subscribe === "function") { property.unsubscribe(); } } orig.apply(); } } }
هذا AutoUnsub هو الديكور الذي يمكن تطبيقه على الطبقات في مشروعنا الزاوي. كما ترون ، فإنه يحفظ الخطاف الأصلي ngOnDestroy ، ثم ينشئ رابطًا جديدًا ويربطه بالفئة التي يتم تطبيقه عليها. بهذه الطريقة ، عندما يتم إتلاف الفصل ، يتم استدعاء ربط جديد. تبحث وظيفتها من خلال خصائص الفئة ، وإذا عثرت على الملاحظة ، فقم بإلغاء الاشتراك فيها. ثم يستدعي ربط ngOnDestroy الأصلي في الفصل ، إن وجد.
@Component({...}) @AutoUnsub export class AppComponent implements OnInit { observable$; ngOnInit () { this.observable$ = Rx.Observable.interval(1000); this.observable$.subscribe(x => console.log(x)) } }
نحن نطبقه على AppComponent الخاص بنا ولم نعد قلقين بشأن نسيان إلغاء الاشتراك من $ ngOnDestroy الممكن ملاحظته - سيقوم الديكور بعمل ذلك من أجلنا.
لكن هذه الطريقة لها جانب سلبي أيضًا - ستحدث الأخطاء إذا كان المكون الخاص بنا يحتوي على ملاحظة بدون اشتراك.
6. باستخدام tslint
في بعض الأحيان ، قد تكون رسالة من tslint مفيدة لإخبار وحدة التحكم أن مكوناتنا أو توجيهاتنا لم تعلن عن طريقة ngOnDestroy . يمكنك إضافة قاعدة مخصصة إلى tslint لتحذير وحدة التحكم عند تشغيل الوبر أو الإنشاء بحيث لا تحتوي مكوناتنا على الخطاف ngOnDestroy :
إذا كان لدينا مثل هذا المكون دون ngOnDestroy :
@Component({...}) export class AppComponent implements OnInit { observable$; ngOnInit () { this.observable$ = Rx.Observable.interval(1000); this.observable$.subscribe(x => console.log(x)); } }
سوف يحذرنا AppComponent الوبر - لين من ربط ngOnDestroy المفقودة:
$ ng lint Error at app.component.ts 12: Class name must have the ngOnDestroy hook
استنتاج
يمكن أن يؤدي التعلق أو الاشتراك المفتوح إلى حدوث تسرب للذاكرة أو أخطاء أو سلوك غير مرغوب فيه أو ضعف أداء التطبيق. لتجنب هذا الأمر ، نظرنا في طرق مختلفة لإلغاء الاشتراك من مشاريع الملاحظة في الزاوي. وأي واحد لاستخدامه في موقف معين يعود إليك.