Angular: the method produces side effects and returns an Observable

In my current code, there is a function that handles login functionality:

  /**
   * Login with provided username and password
   * @returns {Observable<number>} the status code of the HTTP response
   */
  login(username: string, password: string): Observable<number> {
    let body = new URLSearchParams();
    body.append('grant_type', 'password');
    body.append('username', username);
    body.append('password', password);

    return this.http.post(Constants.LOGIN_URL, body, null)
      .do(res => {
        if (res.status == 200) {
          this.authTokenService.setAuthToken(res.json().auth_token);
        }
      })
      .map(res => res.status)
      .catch(err => { return Observable.throw(err.status) });
  }

The purpose of this function is to attempt a login operation and provide an Observable<number> as feedback regarding the HTTP response status code.

However, I noticed a flaw in the implementation. If the caller of this function does not subscribe to the returned Observable, the do function is not executed, resulting in the failure to save the received auth token.

Upon reviewing the documentation for do, it is explained that this method acts as an observer without triggering any execution unless subscribed to:

Note: this is different from subscribing to the Observable. do only observes existing execution, without triggering it like subscribe does.

To ensure that the side effect of saving the auth token occurs regardless of whether the caller subscribes or not, what steps should be taken?

Answer №1

According to @Maximus, cold Observables, such as an HTTP call, do not emit data until they are subscribed to. This means that the .do() callback will not be triggered.

In contrast, Hot Observables will emit data regardless of whether there is a subscriber or not.

To convert a cold Observable into a ConnectableObservable, you can use the publish() operator. The Observable will start emitting data once its connect() method is called.

login(username: string, password: string): Observable<number> {
  let body = new URLSearchParams();
  body.append('grant_type', 'password');
  body.append('username', username);
  body.append('password', password);
  let request = this.http.post(Constants.LOGIN_URL, body, null)
    .do(res => {
      if (res.status == 200) {
        this.authTokenService.setAuthToken(res.json().auth_token);
      }
    })
    .map(res => res.status)
    .catch(err => {
      return Observable.throw(err.status)
    }).publish();
  request.connect();
  // type assertion because nobody needs to know it is a ConnectableObservable
  return request as Observable<number>; 
}

As mentioned by @Maximus, if the subscription occurs after the ajax call has completed, you will not receive the result. To handle this scenario, you can use publishReplay(1) instead of simple publish(). PublishReplay(n) will replay the last n-th elements emitted by the source Observable to new subscribers.

Answer №2

Assuming you are familiar with the behavior of Promises in previous versions of HTTP in AngularJS, I present the following code:

 login(username: string, password: string): Observable<number> {
    return this.http.post(Constants.LOGIN_URL, body, null)
      .then(res => {
        if (res.status == 200) {
          this.authTokenService.setAuthToken(res.json().auth_token);
        }
      })
      .then(res => res.status)
      .catch(err => { return Observable.throw(err.status) });

The observable returned from this.http.get() call is a cold observable. It will only start executing when someone subscribes to it. Hence, chaining operators won't do anything until subscription.

To make a request and share results with future subscribers, consider using an AsyncSubject:

  sent = false;
  s = new AsyncSubject();

  login(username: string, password: string): Observable<number> {   
    if (!this.sent) {
      this.http.post(Constants.LOGIN_URL, body, null)
        .do(res => {
          if (res.status == 200) {
            this.authTokenService.setAuthToken(res.json().auth_token);
          }
        })
        .map(res => res.status)
        .catch(err => { return Observable.throw(err.status) })
        .subscribe(s);

      this.sent = true;
    }

    return s;
  }

This approach ensures that only one HTTP call is made, and all operators, including do, run once. The result is cached in the AsyncSubject and passed to future subscribers.

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

Preventing redundant function calls in Angular's keyup event

My objective is to: Fetch data by calling a service and binding it on the ngOnit() lifecycle hook. Implement a functionality where, in a text input field, an API call is triggered only after a user stops typing for a second (debouncing between user keystr ...

Testing React Hook Form always returns false for the "isValid" property

When creating a registration modal screen, I encountered an issue with the isValid value when submitting the form. In my local environment (launched by npm start), the isValid value functions correctly without any issues. However, during unit testing us ...

How come ESLint isn't raising any issues about the implicit `any` return value from the function call?

Why is ESLint not flagging the implicit any on this specific line: // The variable `serializedResult` is of type `any`, which is permissible because the return value // of `sendMessage(...)` is a `Promise<any>`. But why doesn't ESLint throw an ...

I'm looking for a way to create a Redux thunk action creator that will return a promise. How

In my code, I have a primary thunk that is triggered by a button click. Within this thunk, I need to invoke another thunk and ensure it completes before proceeding. The second thunk returns a promise. Below is an excerpt of the code in question: export f ...

Vue version 3 is encountering an issue with a module that does not have an exported member in the specified path of "../../node_modules/vue/dist/vue"

After I updated my npm packages, errors started popping up in some of the imports from the 'vue' module: TS2305: Module '"../../node_modules/vue/dist/vue"' has no exported member 'X' The X instances affected inclu ...

Components in Angular 2 rc6 are failing to load

Since upgrading my APP to rc6, I've been experiencing some issues with component loading/rendering. In my APP, I categorize components into routing_components and util_components. Interestingly, the routing_components are working perfectly fine while ...

I'm facing an issue with the background-image binding in Ionic 3 where it does not seem to work, regardless

I'm encountering a perplexing issue with Ionic 3 (specifically involving template binding, I believe, and styles) that has been perplexing me for the past few days. Despite my efforts, I've been unable to pinpoint a solution or identify the cause ...

The mysterious appearance of the <v-*> custom element in Vuetify Jest

Currently, I am in the process of writing unit tests for my project using Jest. The project itself is built on Vue, Vuetify (1.5), TypeScript, and vue-property-decorator. One particular area of focus for me has been creating a basic wrapper for the <v- ...

Production environment experiencing issues with Custom Dashboard functionality for AdminJS

I have successfully integrated AdminJS into my Koa nodejs server and it works perfectly in my local environment. My objective is to override the Dashboard component, which I was able to do without any issues when not running in production mode. However, wh ...

What strategies can I utilize to recycle the properties of my model type?

My API call returns a response in the following format: export interface UserResponse { id: string; is_admin: boolean; last_name: string; phone: string; ... salary: number; } After that, I use the datePipe transform method to conv ...

Preventing recursive updates or endless loops while utilizing React's useMemo function

I'm currently working on updating a react table data with asynchronous data. In my initial attempt, the memo function doesn't seem to be called: export const DataTableComponent = (props: State) => { let internal_data: TableData[] = []; ...

Creating unique navigation bar elements

I'm currently working on an application and facing an issue with the navigation from the Component to NavbarComponent. If you'd like to check out the application, you can visit: https://stackblitz.com/github/tsingh38/lastrada The goal is to hav ...

Issue with Angular 2: Service not reflecting updated variable

I am currently working on creating an API service and trying to assign the data to a variable. However, I am facing an issue where the variable is not updating and ends up being undefined when I try to log it after calling the API service. import {Compone ...

Data is not being returned by an Angular 4 HTTP service call

I am encountering an issue with my network.service.ts file. The code is as follows - import { Injectable } from '@angular/core'; import { Http } from '@angular/http'; import { Peer } from '../model/Peer'; import 'rxjs/ ...

Every time I try to deploy my app on Heroku, it keeps crashing with a strange error popping up on the home screen

Here are the logs for my application: 2018-07-19T01:40:27.548845+00:00 app[web.1]: at Object.<anonymous> (/app/node_modules/bcrypt/bcrypt.js:6:16) 2018-07-19T01:40:27.548847+00:00 app[web.1]: at Module._compile (module.js:652:30) 2018-07-19T01:40:27 ...

Tips for efficiently saving data using await in Mongoose

Currently, the code above is functional, but I am interested in utilizing only async/await for better readability. So, my query is: How can I convert cat.save().then(() => console.log('Saved in db')); to utilize await instead? The purpose of ...

Broadcasting events across the entire system

I'm trying to accomplish something specific in Angular2 - emitting a custom event globally and having multiple components listen to it, not just following the parent-child pattern. Within my event source component, I have: export class EventSourceCo ...

What is the reason behind the component being generated only twice?

In my separate module, I have imported a routing module containing routes structured as follows: const routes: Routes = [ { path: '', children: [ { path: 'orders', comp ...

In fact, retrieve the file from an S3 bucket and save it to your local

I've been attempting to retrieve an s3 file from my bucket using this function: async Export() { const myKey = '...key...' const mySecret = '...secret...' AWS.config.update( { accessKeyId: myKey, secretAcces ...

Are you looking to discontinue receiving updates from the @Output EventEmitter?

Explore the Angular website where you can find a demonstration of communication between Parent and Child components through @Output() onVoted = new EventEmitter<boolean>();. Check it out below. In this specific scenario, is it necessary to unsubscri ...