If a task is currently ongoing, be sure to subscribe to it; otherwise, restart it

If I have a long-running observable called longObservable, which emits a new string once after 5 seconds and then completes.

longObservable(): Subject<string> {
    return timer(5000).pipe{
        map(() => randomString())
    }
}

Other pages call this observable multiple times. If it's already running, I want to continue that process. If it's completed, I want to start it again.

longObservable.subscribe() // Starts the timer immediately

This is followed by another subscription two seconds later:

longObservable.subscribe() // Will get the same string as
                            // the previous subscription in 3 seconds.

Finally, a third subscription runs 20 seconds later:

longObservable.subscribe() // Initiates a new iteration and waits
                            // 5 seconds before emitting a new string.

The second subscription works as intended, but I am facing difficulties with the third one. It immediately emits the same value because longObservable has already completed.

This setup is used for geolocation requests on a device. The goal is to request a new location, or use the existing result if a request is already in progress.

Edit: Changed the observable to a subject for multicasting and removed the take(1) operator.

Edit2: https://stackblitz.com/edit/angular-venpk4 contains a working example of what I'm trying to achieve. I hope to accomplish this without using the timerRunning variable and with the help of RxJS operators. This code can be found under the hello component and prints results to the console.

Answer №1

Dealing with a challenging problem led me to come up with a solution showcased on StackBlitz. One of the crucial components in solving this issue is the clever use of the share() operator, which efficiently converts the observable into a subject without explicitly declaring one. The key concept here is creating a new subject each time the previous one completes, achieved by implementing a factory function to either deliver the existing shareable Observable (if longObservable() is still running) or assemble a fresh one.

Highlighted below are the essential sections from the StackBlitz:

let inProgress: boolean = false;

function longObservable(): Observable<string> {
    return timer(5000).pipe(
        map(() => randomString()),
        tap(() => inProgress = false),
        share()
    )
}

let obs$: Observable<string>;

function getLongObs(): Observable<string> {
    if (inProgress) {
        return obs$
    } else {
        inProgress = true;
        obs$ = longObservable();
        return obs$;
    }
}

console.log('initiate first subscribe');
getLongObs().subscribe(
    rand => console.log(`First subscribe returned ${rand}`)
);

setTimeout(() => {
    console.log('initiate second subscribe');
    getLongObs().subscribe(
        rand => console.log(`Second subscribe returned ${rand}`)
    );
}, 2000);

setTimeout(() => {
    console.log('initiate third subscribe');
    getLongObs().subscribe(
        rand => console.log(`Third subscribe returned ${rand}`)
    );
}, 7000)

I trust this explanation aids in understanding the solution!

Answer №2

To achieve what you're looking for, consider using the share() pipe. An example implementation would look like this:


    export class AppComponent {
        private _longObservable: Observable<string> = null

        constructor() {
            this._longObservable = timer(5000).pipe(
                // This will indicate when the timer emits a value to demonstrate that both subscriptions below are sharing the same "execution"
                tap(() => console.log("Timer Triggered!")), 
                map(() => randomString()),
                share()
            );
        }

        ngOnInit() {
            // These two subscriptions will share the observable,
            // as the long observable has not completed by the time the second
            // subscription is triggered.
            this._longObservable.subscribe(console.log);
            setTimeout(() => this._longObservable.subscribe(console.log), 2000);

            // This subscription occurs after 5 seconds.
            // Since timer is a cold observable, this will trigger it to run again.
            setTimeout(() => this._longObservable.subscribe(console.log), 7000);
        }
    }

Output:


Timer Triggered!
randomString1
randomString1
Timer Triggered!
randomString2

If you're unfamiliar with the distinction between hot and cold observables, check out this article: https://medium.com/@benlesh/hot-vs-cold-observables-f8094ed53339

In Angular, HTTP requests and timer(5000) are examples of cold observables.

For more information on the share pipe, visit: https://www.learnrxjs.io/operators/multicasting/share.html

Answer №3

In the demonstration you provided, it is evident that your function generates a fresh Observable each time it is subscribed to. One suggestion for your service could be to establish a variable to retain this Observable. Perhaps using a BehaviorSubject would offer an improved approach. By implementing this variable as a BehaviorSubject, you can subscribe to it anywhere in your codebase, ensuring consistency by always accessing the same Observable instance.

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

Angular 9 does not include the mat-radio-group component within its known modules

After upgrading from Angular 2 to Angular 9, I encountered the following error: src/app/contact-list/contact-list.component.html:7:5 - error NG8001: 'mat-radio-group' is not a known element: 1. If 'mat-radio-group' is an Angular compo ...

AWS lambda not properly displaying static content for an Angular application when running Node Express server

Recently, I encountered an issue while trying to deploy my Angular application on AWS Lambda. I kept receiving a 403 exception for my static content, even though I used Express.js to configure the server. You can check out the problems I'm facing at t ...

You need to provide 1 type argument(s) for the Generic type ModuleWithProviders<T> in Angular 10 Swagger Codegen

Currently, I am generating Codegen proxies using . Upon implementing this in Angular 10, I encountered the following error. How can this issue be resolved? The error message reads: 'Generic type 'ModuleWithProviders' requires 1 type argume ...

TypeScript failing to infer type from return value of class method

Currently, I am developing a class where the constructor calls one of its methods toJSON and sets the return value to an instance property: class Example { property; snapshot; constructor(){ this.property = 'property' if (Math.ran ...

The combination of Observable streams in combineLatest will persist even if one encounters a

I have a function designed to retrieve multiple documents from Firebase. fetchDocuments(documentIds: string[]): Observable<TreeNodeDocument[]> { const observables = []; for(let id of documentIds){ observables.push(this.fetchDocument( ...

What is the best way to retrieve the final entry from a JSON file while using json server with Angular?

I'm currently working with a JSON file where I am making post requests followed by get requests. My goal is to retrieve the latest record in each get request after submitting a post request. For example: [ { "id": 1, "title&qu ...

What is the best way to ensure that a div containing lengthy text wraps to the next line as if it were only text overflow?

Is there a way to make a div or span within another div act as text, causing overflow to shift to the next line? I'm unsure of which CSS properties would achieve this effect. I've attempted using overflow-wrap: break-word; word-break: break-al ...

Angular 2 testing error: Unable to connect to 'ngModel' as it is not recognized as a valid property of 'input'

Currently, I am experimenting with angular2 two-way binding for the control input. Below is the issue that I encountered: An error occurred: Can't bind to 'ngModel' since it isn't a known property of 'input'. Contents of app. ...

Exploring the world of publishing Angular 2 applications

I recently created an Angular 2 application using npm, but as a beginner I am unsure of some aspects. For instance, when I publish my application, I typically use npm publish to share it on my npm account online. However, I am wondering if there is a way t ...

Changing dot notation to bracket notation in Angular

Having trouble using dynamic columns in a Primeng table? The column fields need to be in bracket notation for this setup. What if you have a column with nested JSON structure and you're not sure how to write that in bracket notation? Don't worry, ...

How can I dispatch multiple actions simultaneously within a single epic using redux-observable?

I am a newcomer to rxjs/redux observable and have two goals in mind: 1) enhance this epic to follow best practices 2) dispatch two actions from a single epic Many of the examples I've come across assume that the API library will throw an exception ...

What could be the reason behind my Ionic app receiving a 401 error from an API call while Postman is not encountering the

Following the instructions from a tutorial on Smart of the Home, I implemented the code below in a provider for my Ionic 2 App. return new Promise(resolve => { // Using Angular HTTP provider to make a request and process the response let headers = ...

Angular 4 - Automatically scroll to specific list item based on search query input

While I can achieve this with custom JavaScript, I'm curious if Angular 4 has any built-in features that could help. I have a list of checkboxes that are scrollable and a search input above them. My goal is to enable users to quickly jump to a section ...

Tips for configuring VS Code to automatically change a callable property to an arrow function instead of a standard function

When interacting with ts/tsx files in VS Code, the autocompletion feature for callable properties offers two options: propertyName and propertyName(args): https://i.sstatic.net/BFVTm.png However, selecting the second option generates a standard function: ...

Leveraging services in Angular: accessing directly in view or via component

I am currently working on an application that consists of multiple pages, each with their own components. I have a single config.service.ts file where the debug mode is set as a boolean. Within my views, depending on this setting, I need to show buttons, ...

How can I effectively build an Angular Library containing various components?

I am currently restructuring the core elements that form a search page into an angular library. My goal is to include various components in this angular library, such as SearchControls, PageHeader, PageFooter, and more. I intend to insert <search-contro ...

Ways to identify modifications from a BehaviorSubject and automatically trigger a response according to the updated value

I am currently implementing a BehaviorSubject for managing languages in my Angular project. I am also utilizing Angular Datatables and trying to dynamically change the language displayed in the data table based on the value returned from the subscription. ...

Execute --runTestsByPath on two or more distinct paths

Currently, I am utilizing the jest cli for running my tests. Jest offers a useful cli option known as --runTestsByPath, which allows me to specify the locations of my tests. Despite having unit tests spread out in various directories within my repository, ...

Angular version 5 and above introduces a new feature called "openFromComponent" within the Snackbar component, facilitating seamless communication

Angular (v5.2.10) Snackbar --| Introduction |-- I am facing a scenario where an Angular component named "Parent" is initializing an Angular Material Snackbar known as snackBar. The snackbar is being passed in the component called SnackbarMessage, which ...

Implement the properties encapsulation design pattern

In need of a method to control document activation logic, where activating a document involves adding it to a list of activeDocuments and setting a flag to true. Direct access to the isActive property should be prohibited. class DocumentService { pr ...