Prevent API requests with a toggle button in RxJS to start and stop calls

I have been diving into Rxjs and managed to put together a simple slideshow that updates the images every 5 seconds after clicking the start button. However, I am struggling to figure out how to pause/stop the API fetching process once the button is clicked again.

import './style.css';
import { tap, switchMap } from 'rxjs/operators';
import { fromEvent, Observable, timer } from 'rxjs';

function updateImages(
  links: string[]
): void {
  document.getElementById('slideshow').childNodes.forEach((node: ChildNode) => {
    if (node.nodeType == Node.ELEMENT_NODE) {
      if (links.length) {
        let element: HTMLElement = node as HTMLElement;

        element.classList.add('loading');
        element.style.backgroundImage = "url('" + links.shift() + "')";
        element.classList.remove('loading');
      }
    }
  });
}

const apiUrl: string = 'https://dog.ceo/api/breeds/image/random';

const btn = document.getElementById('btn');

const btnEvents$ = fromEvent(btn, 'click');

const sub = btnEvents$.subscribe((result) => {
  startPolling('dogs').subscribe((dogs) => {
    updateImages(dogs.map(({ message }) => message));
  });
  btn.innerHTML = 'Stop';
  console.log(btn.classlist);
});

function requestData(url: string): Observable<Array<{ message: string }>> {
  return Observable.create((observer) => {
    Promise.all([
      fetch(url).then((response) => response.json()),
      fetch(url).then((response) => response.json()),
      fetch(url).then((response) => response.json()),
      fetch(url).then((response) => response.json()),
    ])
      .then((dogs) => {
        console.log(dogs);
        observer.next(dogs);
        observer.complete();
      })
      .catch((err) => observer.error(err));
  }).pipe(tap((data) => console.log('dogrequest', data)));
}
function startPolling(
  category: string,
  interval: number = 5000
): Observable<Array<{ message: string }>> {
  const url = category === 'dogs' ? apiUrl : null;
  console.log(url);
  return timer(0, interval).pipe(switchMap((_) => requestData(url)));
}

Right now, the data keeps being fetched continuously and I can't make it stop.

Answer №1

If I understand correctly what you are attempting to achieve, it seems like you have a button that functions as a switch. When clicked, it starts the slideshow (switch on) and when clicked again, it stops the slideshow (switch off).

If my interpretation is accurate, utilizing the switchMap operator would be suitable in this scenario.

Lets explore how this can be implemented. The code snippets below provide explanations along the way:

// We start by defining a variable to hold the state of the switch
// While there are more 'rxJs idiomatic' methods to manage states,
// we'll stick with a basic variable for now
let on = false;

// This part sets up the btnEvents$ Observable which serves as the starting point for our stream
const btn = document.getElementById('btn'); // obtain the button element
const btnEvents$ = fromEvent(btn, 'click');

// Here lies the core stream implementing our solution
// It commences with the stream of 'click' events
btnEvents$
  .pipe(
    // The tap operator enables us to perform side effects
    // On every click event notification, we update the state (switch on/off) and set the button label
    tap(() => {
      on = !on;
      btn.innerHTML = on ? 'Stop' : 'Start';
    }),
    // This is where the switching occurs 
    // Whenever a click event is detected upstream, switchMap unsubscribes from the previous Observable and subscribes to a new one
    switchMap(() => {
      // Upon a new click event being received, we check the current state
      // If the state is "on", we return the stream that retrieves the data
      // Otherwise, an Observable signaling an empty array is returned
      return on ? requestData("dogsUrl") : of([]);
    }),
    // Finally, another side effect - updating the images based on the fetched dog data
    // Bear in mind, if the state is "off", the "dogs" array will be empty and hence no updates will occur
    tap(dogs => updateImages(dogs.map(({ message }) => message));)
  )
  // With the Observable structured per our requirements,
  // we simply need to subscribe to trigger the entire execution
  .subscribe(console.log);

Please note that the implementation remains incomplete, particularly lacking error handling logic, but hopefully, this sheds light on the concept.

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

Leveraging WebStorm's TypeScript support in conjunction with node_modules

Attempting to set up a TypeScript project in WebStorm to import a Node.js module has been a struggle. I made sure to download the necessary TypeScript definition in settings and specified --module commonjs in the compiler settings. However, I keep running ...

Having trouble pinpointing the specific custom exception type when using the throw statement in TypeScript?

I have run into a problem while using a customized HttpException class in TypeScript. Let me show you how the class is structured: class HttpException extends Error { public status: number | undefined; public message: string; public data: any; ...

Modifying the property of an object following retrieval from the server

I find myself a bit perplexed about the optimal approach for modifying a value in an object once it has been retrieved from a server. Here is my TypeScript Script: import { Component, OnInit } from '@angular/core'; import { Response } from &apo ...

I need to access the link_id value from this specific actionid and then execute the corresponding function within the Ionic framework

I have a JavaScript code in my TypeScript file. It retrieves the attribute from a span element when it is clicked. I want to store this attribute's value in a TypeScript variable and then call a TypeScript function. Take a look at my ngOnInit method, ...

How can I transform this statement into a higher-order function that offers a resource instead of using an object for initialization and destruction?

Starting with this code snippet: convert utilizes svgInjector to start and terminate a resource. export async function convert( serializedSvg: string, svgSourceId: string, containerId: string ): Promise<string> { const svgInjector = new SvgI ...

Having trouble exporting constants and utilizing the "use server" function in actions.ts within Next.js?

I am currently developing an application using Next.js and I have a file called actions.ts where all my functions for fetching and adding data from my Vercel Postgres store are stored. After these functions, I want to revalidate the data so I included expo ...

Nestjs opts to handle invalid routes by throwing a NotFoundException rather than a MethodNotAllowed

I've recently developed a Rest API using NestJS and now I'm focusing on customizing error responses. Specifically, I want to address the scenario where a user calls an endpoint with the incorrect HTTP method. Take for instance the endpoint GET / ...

Guide to monitoring updates to a universal server-side variable in Angular 2

I am currently developing an application using Angular 2 with Electron and Node. The tests are executed on the server, and the results are stored in a global variable array named testResults. I am able to access this array in Angular by using: declare var ...

Verify the data type of the returned information from the graphql query

Within my code, I am utilizing a graphql query through a hook that has been automatically generated by Codegen. Codegen not only creates return types but also all data types required. According to the types defined by codegen, the expected return type of m ...

Why is there a discrepancy between the value displayed in a console.log on keydown and the value assigned to an object?

As I type into a text box and log the keydown event in Chrome, I notice that it has various properties (specifically, I'm interested in accessing KeyboardEvent.code). Console Log Output { altKey: false bubbles: true cancelBubble: false cancelable: t ...

Troubleshooting History.push issue in a Typescript and React project

Currently, I'm tackling a project using React and TypeScript, but I've encountered a problem. Whenever I attempt to execute a history.push function, it throws an error that reads: Uncaught (in promise) TypeError: history.push is not a function. ...

Strategies for efficiently managing multiple subscriptions in an Angular form using minimal code and best practices

I am currently working on an Angular form that includes multiple select options managed by Subscriptions. However, I find myself writing a lot of code for each Subscription and would like to improve this process. Could someone provide some best practices ...

What is the process for performing interpolation in Angular version 8?

In my Angular project, I have 2 components working together. Component A sends an id using the following code snippet: <a [routerLink]="['/numbersbyareacode', element.id]"> {{element.title}} </a> Upon navigating to Component B, ...

The Element is Unfamiliar - Application with Multiple Modules

I seem to be facing an issue with how my modules are structured, as I am unable to use shared components across different modules. Basically, I have a Core module and a Feature module. The Core module contains components that I want to share across multip ...

The callback function does not seem to work when using MUI v4 PropFunc

Encountering an issue with custom styling using PropFunc in Material UI (MUI) v4 (4.12.4). When providing a PropFunc in the traditional callback way to get CSS, it works as expected: const useStyles = makeStyles((theme) => { return { input: { ...

linting tool promises there are no missing attributes

When utilizing a component that connects to 'redux' and requires properties to be passed, the linter complains about missing properties. Component1 interface Component1Props { id: number; users: any[]; } const Component1: React.FC<Com ...

Condition for button functionality

I have a Submit button that, when pressed, triggers the onSubmit function. <b-form @submit.stop.prevent="handleSubmit(onSubmit)"> ... <b-button type="submit" >Submit</b-button> <script lang="ts ...

Creating a typescript type for contextual dispatch by leveraging the values of another interface

I am seeking to define a specific type for my "reducer" function. The "reducer" function I have takes in 2 parameters: the current state and the data sent in the dispatch context (to be used by the reducer). const reducer = ( state: any, props: { ...

In TypeScript, how to refer to the type of the current class

Is there a way to reference the current class type in the type signature? This would allow me to implement something like the following: export class Component{ constructor(config?: { [field in keyof self]: any }) { Object.assign(this, config) ...

I am interested in retrieving a particular item from the data() function in Firestore

snapshot.forEach(doc => { console.log("ID: "+doc.id, '=>', "Doc DATA: "+JSON.stringify(doc.data())); }); I am looking to extract just one item from doc.data(), which is an array of strings named "supportedCurrencies". Can someone guide m ...