Is it possible to utilize a one-time subscription to an HTTP action inside an Angular service? Are there any drawbacks to this approach?
public async signInAsync(userName: string, password: string): Promise<void> {
try {
const token = await this.authenticate(userName, password).pipe(first()).toPromise();
this.signInCompleted(token);
} catch {
this.signInFailed();
}
}
In order to implement the OnPush change detection strategy and maintain "business logic" within services, observables are used by services to expose values to components. Components then subscribe to these values using the async pipe. Only service functions can modify the state by calling
this.patchState('actionName', {...})
.
protected signInCompleted(token: string): void {
this.localStorageService.setItem(LocalStorageKey.AUTH_TOKEN, token);
this.patchState('signInCompleted', {
isAuth: true,
token: token,
error: null
});
this.router.navigate([AppRoute.AUTH]);
}
Therefore, when using HttpClient, it's necessary to subscribe to the returned observable in some way.
I initially used a simple subscription:
protected signIn(...): void {
this.authenticate(..).subscribe(..);
}
However, I discovered that this method wasn't easy to test since it's unclear when the call is executed, and async()
doesn't interact well with observables. To ensure testability, I had to make it asynchronous and convert it to a promise. Still, I'm uncertain if there are any disadvantages to subscribing with pipe(first()).toPromise()
.
I also considered using
pipe(map(...)).pipe(catchError(...))
, but I'm unsure how to bind the action to the component or if this approach is superior to the previous one.
public signIn(userName: string, password: string): Observable<void> {
return this.authenticate(userName, password).pipe(map(...)).pipe(catchError(...));
}