أنغولار - اشتراك غير مرغوب فيه على خدمة الملاحظات
اتباع نمط الخدمة المؤقتة، أنا أستخدم BehaviourSubject يتم الاشتراك فيه باستخدام Observable للقراءة فقط تم إنشاؤه منه في مكونات مختلفة
// credential.service.ts
private _credentialList$: BehaviorSubject<Credential[]>
readonly credentialListObv$: Observable<Credential[]>
...
this. credentialListObv$ = this.credentialList$.asObservable()
...
getCredentialList(...): Observable<Credential[]> {
return this.http.get<Credential[]>(
``
).pipe(
tap(credList => {
...
this._credentialList$.next(credList)
}),
)
}
لذلك من المكون أقوم باستدعاء طريقة getCredentialList لإصدار القائمة المسترجعة بواسطة الموضوع _credentialList$ الحصول على القيمة.
المشكلة هي أنه عندما أقوم بإنشاء مكوني، سأحصل على اشتراك غير مرغوب فيه عند استدعاء طريقة getCredentialList
// credential-list.component.ts
private _credentials$: Observable<Credential[]>
constructor(
private credentialService: CredentialService,
) {
this._credentials$ = this.credentialService.credentialListObv$.pipe(
tap(() => {console.log("sub")}),
...
)
}
ngAfterViewInit(): void {
this.credentialService.getCredentialList(...).subscribe()
}
get credentials$() {
return this._credentials$
}
// credential-list.component.html
<mat-expansion-panel *ngFor="let credential of credentials$ | async; let i=index">
...
النتيجة هي أنه عندما أصل إلى الصفحة سيتم طباعة "sub" مرتين. الاشتراك الأول يحدث من خلال أنبوب async داخل قالب المكوّن، ولكن لماذا يوجد اشتراك آخر إذا لم أقم أبدًا باستدعاء subscribe على this._credentials$ من مكون credential-list؟
الإجابة رقم 1
لأنك تستخدم BehaviourSubject، فإن الانبعاث الأول هو القيمة الأولية له. ثم، لاحقًا، في NgAfterViewInit، تقوم بالاشتراك في getCredentialList الذي يدفع قيمة جديدة إلى نفس BehaviourSubject.
حاول تسجيل القيم، بدلاً من السلسلة المكتوبة:
tap((val) => {console.log(val)}),يجب أن يكون الأول هو الافتراضي الذي استخدمته عند تهيئة الموضوع (this._credentialList$=new BehaviorSubject(...)
الإجابة رقم 2
ذلك لأنك أيضاً تشترك في طريقة الخدمة التي تعيد المراقب HTTP. لذلك الآن يحتوي المكون الخاص بك على اشتراكين نشطين لاستدعاء واحد على HTTP.
طرق HTTP في Angular تكتمل بعد إصدار قيمة واحدة، لذا يمكنك الاشتراك بأمان في الـ observable داخل الطريقة.
getCredentialList(...): Observable<Credential[]> {
this.http.get<Credential[]>(
``
).pipe(
tap(credList => {
...
this._credentialList$.next(credList)
}),
).subscribe();
}
ثم في مكونك، اترك المراقب الخاص بك كما هو، لكن لا تشترك في الطريقة.
ngAfterViewInit(): void {
this.credentialService.getCredentialList(...);
}
الآن، استدعاء طريقة الخدمة يؤدي إلى تشغيل طلب HTTP، ولا يزال الـ _credentials$ الخاص بك يلتقط البيانات من الـ BehaviorSubject.
تعديل: بما أنك قلت إنك لا تريد الاشتراك في أي شيء داخل الخدمة، يمكنني أن أقدم لك حلاً يتطلب اشتراكًا واحدًا فقط في قالب المكون.
// credential.service.ts
private credentialParams$ = new Subject<ApiParameters>();
public credentialListObv$: Observable<Credential[]>;
...
this.credentialListObv$ = this.credentialParams$
.pipe(
switchMap(urlParameters=>
this.http.get<Credential[]>(`url with ${urlParameters}`)
)
);
...
getCredentialList(newParams:ApiParameters): void {
this.credentialParams$.next(newParams);
}
استخدم الموضوع لإصدار أي معلمات تحتاجها لاستدعاء واجهة برمجة التطبيقات الخاصة بك. ثم يقوم المراقب بأخذ تلك المعلمات الصادرة وإرجاع مراقب الطلب HTTP مباشرة.
// credential-list.component.ts
public credentials$: Observable<Credential[]>
constructor(
private credentialService: CredentialService,
) {
this.credentials$ = this.credentialService.credentialListObv$.pipe(
tap(() => {console.log("sub")}),
...
);
}
ngAfterViewInit(): void {
this.credentialService.getCredentialList(apiParams);
}
// credential-list.component.html
<mat-expansion-panel *ngFor="let credential of credentials$ | async; let i=index">
في المكون، نحن نشترك فقط في المراقب الخاص بالخدمة. عند دورة حياة التهيئة، نستدعي طريقة الخدمة. هذا يجعل المراقب الخاص بالمكون يستجيب لطلب HTTP الذي سيتم إرجاعه من مراقب الخدمة.
ملاحظة: لا بأس بالاشتراك في شيء داخل خدمة، إذا كنت تعرف أن الـ Observable سينتهي (مثل طلبات HTTP).
