Vyhledávání na webu

angular - Nechtěné přihlášení k odběru na observable službě


Následující vzor ukládání služby do mezipaměti používám BehaviourSubject, na který se přihlásím pomocí pouze ke čtení Observable vytvořeného z něj v různých komponentách

// 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)
      }),
    )
  }

Takže z komponenty spouštím metodu getCredentialList, aby emitovala načtený seznam prostřednictvím subjektu _credentialList$ a všichni odběratelé credentialListObv$ by měli obdržet hodnotu.

Problém je v tom, že když vytvořím svou komponentu, získám nežádoucí odběr, když zavolám metodu 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">
        ...

Výsledek je takový, že když přistoupím na stránku, bude "sub" vytištěn dvakrát. První přihlášení se stane z async pipe uvnitř šablony komponenty, ale proč je tam další, když nikdy nebudu volat subscribe na this._credentials$ z komponenty credential-list?

Odpověď č. 1

Protože používáte BehaviourSubject, první vysílání je jeho počáteční hodnota. Poté se později, v NgAfterViewInit, přihlásíte k odběru getCredentialList, který pošle novou hodnotu do stejného BehaviourSubject.

Zkuste zaznamenat hodnoty místo zadaného řetězce:

tap((val) => {console.log(val)}),

První by měla být výchozí, kterou jste použili při inicializaci subjektu (this._credentialList$=new BehaviorSubject(...)

Odpověď č. 2

To je proto, že se také přihlašujete k metodě služby, která vrací HTTP observable. Teď má váš komponent dvě aktivní přihlášení k jednomu HTTP volání.

HTTP metody v Angularu se dokončí po odeslání jedné hodnoty, takže se můžete bezpečně přihlásit k odběru observable uvnitř metody.

getCredentialList(...): Observable<Credential[]> {
    this.http.get<Credential[]>(
      ``
      ).pipe(
      tap(credList => {
        ...
        this._credentialList$.next(credList)
      }),
    ).subscribe();
  }

Pak ve vaší komponentě nechte svůj observable stejný, ale nepřihlašujte se k metodě.

ngAfterViewInit(): void {
  this.credentialService.getCredentialList(...);
}

Nyní vyvolání metody služby spustí HTTP požadavek a vaše soukromá proměnná _credentials$ observable stále získává data z BehaviorSubject.


Úprava: Protože jste řekl, že se nechcete k ničemu uvnitř služby přihlašovat, mohu vám nabídnout řešení, které vyžaduje pouze jedno předplatné v šabloně komponenty.

// 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);
}

Použijte subjekt k vydání jakýchkoli parametrů, které potřebujete pro volání API. Poté observable vezme tento vydaný parametr a přímo vrátí observable HTTP požadavku.

// 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">

V komponentě se pouze přihlašujeme k observablu služby. Při inicializačním životním cyklu voláme metodu služby. To způsobí, že váš komponentní observable zareaguje na HTTP požadavek, který bude vrácen z observable služby.

P.S. Je v pořádku se přihlásit k odběru něčeho v rámci služby, pokud víte, že observable se dokončí (například HTTP požadavky).