Siguiendo el patrón de servicio en caché, estoy usando un BehaviourSubject suscrito usando un Observable de solo lectura creado a partir de él en varios componentes
// 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) }), ) } Entonces, desde el componente, activé el método getCredentialList para emitir la lista obtenida por el sujeto _credentialList$ y, por lo tanto, todos los suscriptores de credentialListObv$ deberían obtener el valor.
El problema es que cuando creo mi componente obtendré una suscripción no deseada cuando invoco el método 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"> ... El resultado es que cuando accedo a la página "sub" se imprimirá dos veces. La primera suscripción ocurre desde la canalización async dentro de la plantilla del componente, pero ¿por qué hay otra si nunca invocaré subscribe en this._credentials$ del componente credential-list ?
Esto se debe a que también se está suscribiendo al método de servicio que devuelve el HTTP observable. Entonces ahora su componente tiene dos suscripciones activas a una llamada http.
Los métodos HTTP en Angular se completan después de emitir un valor, por lo que puede suscribirse de manera segura al observable dentro del método.
getCredentialList(...): Observable<Credential[]> { this.http.get<Credential[]>( `` ).pipe( tap(credList => { ... this._credentialList$.next(credList) }), ).subscribe(); }Luego, en su componente, deje su observable igual, pero no se suscriba al método.
ngAfterViewInit(): void { this.credentialService.getCredentialList(...); } Ahora, invocar el método de servicio activa la solicitud HTTP, y su observable privado _credentials$ aún obtiene los datos del BehaviorSubject.
Editar: dado que dijo que no desea suscribirse a nada dentro del servicio, puedo ofrecerle una solución que solo requiere una suscripción en la plantilla del componente.
// 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); }Utilice el asunto para emitir los parámetros que necesite para su llamada a la API. Luego, el observable toma ese parámetro emitido y devuelve directamente el observable de solicitud 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">En el componente, solo nos suscribimos al servicio observable. En el ciclo de vida init, invocamos el método de servicio. Esto hace que su componente observable reaccione a la solicitud HTTP que se devolverá desde el servicio observable.
PD Está bien suscribirse a algo dentro de un servicio, si sabe que se completa observable (como solicitudes HTTP).
Debido a que usa BehaviourSubject , la primera emisión es el valor inicial de la misma. Luego, más tarde, en NgAfterViewInit , se suscribe a getCredentialList , que envía un nuevo valor al mismo BehaviourSubject .
Intente registrar los valores, en lugar de la cadena escrita:
tap((val) => {console.log(val)}),
El primero debe ser el predeterminado que usó cuando creó una instancia del sujeto ( this._credentialList$ = new BehaviorSubject(... )