What is the process for updating an observable value once another observable has been modified?

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(...));
}

Answer №1

After experimenting with promises and observables, I've found that using observables makes it easier to cancel calls when a newer request is needed or when a component is destroyed. Although the syntax may not be as clear as async/await in promises and testing can be more challenging at times, the ability to cancel calls is incredibly useful (especially for API calls related to search functionality in components like autocomplete).

As for handling side effects, I recommend creating a new operator:

export function effect<T>(
  completed?: (value: T) => void,
  failed?: (error: any) => void
): OperatorFunction<T, void> {
  return (observable$: Observable<T>): Observable<void> =>
    observable$.pipe(
      tap(completed, failed),
      catchError(_ => of()),
      map(() => {})
    );
}

Use this operator in a service:

public login(userName: string, password: string): Observable<void> {
    return this.loginUsingPOST(userName, password).pipe(
      effect(
        token => this.loginCompleted(token),
        error => this.loginFailed(error)
      )
    );
  }

And subscribe to it only in components:

public submit(): void {
    this.authService
      .login(this.loginForm.value.userName, this.loginForm.value.password)
      .pipe(this.untilDestroy())
      .subscribe();
  }

Credits to @fridoo

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the best way to arrange items by utilizing the Array index in JavaScript?

Currently, I am attempting to make the elements within this angular component cascade upon loading. The goal is to have them appear in a specific layout as shown in the accompanying image. I'm seeking guidance on how to write a function in the TypeSc ...

Discovering React components within a shadow DOM utilizing TypeScript and Protractor [

I am currently faced with the challenge of locating elements within a shadow root from 9-11. Traditional locators like xpath, css, and id have proven unsuccessful in this scenario. However, I was able to successfully locate the element using JavascriptExec ...

Creating a seamless integration between Angular 2's auth guard and observables to enhance application security

I'm having trouble setting up an auth guard for one of my routes because I am unsure how to implement it with an observable. I am using ngrx/store to store my token. In the guard, I retrieve it using this.store.select('auth'), which returns ...

What is the best way to create an interface that consistently uses a generic array?

One of my functions has a general structure: export function limitToApiContraints<T extends Array>(payload: T, type: IQueueTypes) { ... } However, there is an issue with the generic signature that prompts the following error message: The gener ...

Trouble arises when trying to test an Angular service that relies on abstract class dependencies

Currently, I am working on a service that has a dependency on another service, which in turn relies on two abstract classes as dependencies. (ThemeConfigService -> (SettingsService -> SettingsLoader, NavigationLoader)) During testing, the failure oc ...

Display of environment variables in the production build

In a nutshell, I somehow "downloaded" my app (Ctrl+S or right click+save as) and discovered that my environment variables are not hidden but stored in a file named main.xxxx.js (where xxx is the build hash). I came across a secret key for a service in tha ...

Having trouble importing a variable from a precompiled library in TypeScript JavaScript

Here is the content of my package.json file: { "name": "deep-playground-prototype", "version": "2016.3.10", "description": "", "private": true, "scripts": { "clean": "rimraf dist", "start": "npm run serve-watch", "prep": "browserify ...

After pushing to history in React, the rendered component fails to display on the screen

I am in the process of developing a React application. Here are the dependencies I am currently using: "react": "^17.0.2", "react-dom": "^17.0.2", "react-helmet": "^6.1.0", "react-router" ...

Stop the SVG animation in Angular when the loading screen first appears

I would like to incorporate an SVG animation to play while the browser is loading scripts and other assets of Angular 5. Specifically, I intend to use this SVG animation: <?xml version="1.0" standalone="no"?> <!-- Generator: SVG Circus (http://s ...

Angular CLI version 7 experiencing issues with POST requests

When I attempted to send an http request, I followed the guidelines provided in this documentation and created the following service. Although I used Angular version 5 syntax, the function did not execute as expected. createCartProduct(cartpr : CartProdu ...

Adding attributes to an input element in real-time

I am in the process of generating multiple input elements using an *ngFor loop, and for some of them I would like to include a data-bv-integer="true" attribute, while leaving it out for others. The decision of whether or not to include this attribute depen ...

Is there a way to turn off the backdrop of angular material2's side nav?

I'm new to using Angular Material and I am looking for guidance on how to disable the backdrop of a side nav. I have created a working example on Plunker but the backdrop is still enabled. You can view it here. Here's what I've tried so far ...

Error encountered while reading JSON data using Angular4 and TypeScript: Json

When a user selects one or more checkboxes and submits a form, data in the database is updated. At that moment, I call location.reload() from the code to reload the page and display the correct data. Below is the backend web API code: [HttpGet] public as ...

Troubleshooting issues with Bootstrap's responsiveness configuration

I've been working on creating a responsive user login page using Angular 5. It looks great on full-screen display. https://i.sstatic.net/YQrL5.png However, when I resize the browser window, the responsiveness seems to break. https://i.sstatic.net/4 ...

Angular users should be cautious of the 'grid zero width' warning that may arise when employing ag-Grid's sizeColumnsToFit() on multiple ag-Grids simultaneously

I'm encountering an issue with ag-grid where I see the following warning in the console. Despite conducting some research, none of the solutions I found have resolved my problem. It appears that there may be a memory leak within my application based o ...

In Google Chrome, Angular does not display a tool tip when a button is disabled

I've utilized angular along with ng-bootstrap for a project I'm working on. The issue arises when using ngbTooltip to display a tooltip on a button. <button class="btn btn-success" (click)="onSubmit()" [ngbTooltip]="tipContent" ...

Yelp API call resulting in an 'undefined' response

When attempting to make an API call using the yelp-fusion, I noticed that the logged result is showing as undefined. It seems like this issue might be related to promises or async functions. It's important for me to maintain this within a function sin ...

"Dealing with conflicts between RMQ and TypeORM in a NestJS

Every time I try to use TypeOrm, RMQ crashes. I can't figure out why. Utilizing the library golevelup/nestjs-rabbitmq has been a struggle for me. I've spent 7 hours trying to resolve this issue. @Module({ imports: [ ConfigModule.f ...

React Typescript: Turn off spellchecking

Struggling to turn off spell check for typescript <form> <input type='text' name='accountName' ref={accountName} onChange={checkName} spellCheck='false' // <====== Disable spellche ...

Variability in Focus Behavior while Opening a URL in a New Tab with window.open()

Here is a code snippet I have been using to open a URL in a new tab: window.open(urlToOpen, '_blank', 'noopener noreferrer'); The issue I am experiencing is that when this code is executed for the first time, it opens the URL in a new ...