Utilizing a mutual RxJS subject for seamless two-way data binding in Angular 2

I have a unique service dedicated to managing app configurations

class Configuration {
  get setting() {
    return dataStore.fetchSetting();
  }

  set setting(value) {
    dataStore.saveSetting(value);
  }
}

This configuration is linked to components through configuration.setting.

Due to the potentially expensive and asynchronous nature of retrieving settings from the storage, I am considering updating the getter/setter with an RxJS subject that can handle updates and reads from the storage efficiently.

My refactored version looks like this:

class Configuration {

  constructor() {
    this.settingSubject = new ReplaySubject(1);

    settingSubject.subscribe((value) => {
      dataStore.saveSetting(value);
    });

    this.setting$ = this.settingSubject
    .map(() => dataStore.fetchSetting())
    .publishReplay(1).refCount();
}

Now, I can use it as configuration.setting$ | async and update it using

configuration.settingSubject.next(newSetting)
. This method seems to have effectively cached the costly storage retrievals.

However, two issues have surfaced.

The first problem is that both the settingSubject subject and setting$ observable need to be publicly accessible for this operation, even though a subject was intended to act as both an observable and an observer.

Is there a way to convert setting$ into a single Subject property within the Configuration service, allowing it to be subscribed using subscribe(...) and updated with next(...)?

The second concern is about the synchronous behavior of the code.

How can we address this issue when dealing with promises returned by both datastore.retrieve and datastore.save?

Answer №1

Your code seems to be functioning properly, so there may not be much need for suggestions. It appears that you could simplify your code by removing the use of this.foo$.

You can achieve the functionality of storing the latest value using a ReplaySubject. Unless you specifically require calling storage.get('foo') after every storage.set('foo', val);, it might not be necessary.

I have created a live demo with your code: http://plnkr.co/edit/pxjRQr6Q6Q7LzYb1oode?p=preview

Here is a simplified version of your code:

class Setting {

  constructor() {
    var storage = new Storage();

    this.fooSubject = new ReplaySubject(1);
    this.fooSubject.subscribe((val) => {
      storage.set('foo', val);
    });
  }

  get observable() {
    return this.fooSubject.asObservable();
  };

  store(val) {
    this.fooSubject.next(val);
  }
}

I have abstracted the use of a Subject by wrapping it with .asObservable() and providing a store() method for .next() call. An example usage would look like this:

let settings = new Setting();

settings.store('Hello');

settings.observable.subscribe(val => console.log(val));
settings.store('Hello 2');
settings.store('Hello 3');

settings.observable.subscribe(val => console.log(val));
settings.store('Hello 4');

Output in console:

Hello
Hello 2
Hello 3
Hello 3
Hello 4
Hello 4

Note that the ReplaySubject is not initialized with any value. Even if you call setting.fooSubject.next(newFoo) right after creating the ReplaySubject, it will still be stored again upon subscription with storage.set('foo', val);.

About the synchronous issue, although your code is asynchronous, it runs sequentially. If storage.get('foo') involves a time-consuming synchronous operation, consider moving it to a Web Worker due to JavaScript's single-threaded nature.

Answer №2

Creating a new subject with side effects can be easily achieved by extending the AnonymousSubject class, which is used in the Subject.create(...) factory method. The resulting subject will have properties called destination and source that store the original subject and observable.

class CustomSharedSubject extends AnonymousSubject {
    constructor() {
        const subject = new BehaviorSubject('');

        const observable = subject.asObservable()
        .mergeMap((value) => promisedStorage.get('custom'))
        .publishReplay(1)
        .refCount();

        super(subject, observable);
    }

    next(value): void {
        promisedStorage.set('custom', value)).then(
            () => this.destination.next(value),
            () => this.destination.error(value)
        );
    }
}

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

Showing or hiding components within ReactJS based on conditions from other components

A custom hook has been created to toggle the visibility of a list when a button is clicked. Below is the snippet of the custom hook: import { useEffect } from "react"; import { useState } from "react"; function useVisibilityStatus() { ...

What is the reason for not requiring checks with Union Types when utilizing a variable, yet necessitating them within a function?

Currently working on some Typescript challenges and encountered a scenario involving a union type. In this example, the function getIstanbulPostalCode is declared to return either a string or a number: function getIstanbulPostalCode(): string | number { ...

Leveraging the Railway Pathway from the Google Maps API

I need to customize my map to display only railway stations instead of the entire map. How can I achieve this? Below is the code I have attempted: <html> <head> <style type="text/css"> html { height: 100% } ...

What causes a Next.js App to crash when a prop is not defined in destructuring code?

Let me share the issue I am facing. I have developed a custom Context API wrapper to handle all my data. However, there is this docType property that may not always be defined or may not exist at times. When I destructure it in this way: const { docType } ...

What could be causing my jQuery script to malfunction?

After scouring through numerous Stack Overflow questions and conducting countless Google searches, I am still stumped by the issue at hand. As a beginner in web development, all I want is for this simple page to function properly. Can someone please point ...

Limiting the display of every item in Angular ngFor

I'm currently working with Angular and I have the following code in my component.html: <div class="card" *ngFor="let item of galleries;" (mouseenter)=" setBackground(item?.image?.fullpath)" (mouseover)="showCount ...

Quickly send off an Angular 4 HTTP POST request and move on

I've been experimenting with making a fire and forget request, but none of my attempts seem to be working as expected. The situation is that after completing one subscribable request, I need to redirect to another page. However, before the redirectio ...

Organize data in a table using a dropdown selection menu

My data structure looks like this: $scope.friends = [ { name:'John', phone:'555-1212', age:10 }, { name:'Mary', phone:'555-9876', age:19 }, { name:'Mike', phone:'555-4321', age:21 }, { na ...

Is there no body sent in the $.ajax post request?

My server is returning an error when I try to make a simple post request. It's saying that the post request has no body and all the keys have an "undefined" value. Here is the code for my post request: let alert_title = 'Alert'; let alert ...

Code error TS2345 occurs when assigning the argument of type '{ headers: HttpHeaders; }' to a parameter of type 'RequestOptionsArgs'. This indicates a mismatch in the type of data being passed, causing an

Upon running ionic serve, these are the results that I am encountering. My setup consists of Ionic4 version with Angular 8. While executing the command, this error appears: src/app/home/home.page.ts:60:77 - error TS2345: Argument of type '{ headers ...

several javascript onclick event listeners for manipulating the DOM

I am currently experimenting with d3 and attempting to create a simple webpage to showcase my d3 examples for future reference. The page displays the output of the d3 code (specifically, the force layout from Mike Bostock) and then embeds the correspondin ...

Establishing the maximum width of a Vuetify expansion panel component

I'm currently in the process of developing a webpage using vuetify and nuxt. My main focus right now is adjusting the max-width property of the expansion panel UI component (https://vuetifyjs.com/en/components/expansion-panels). Here's the code s ...

Customize your markers on Google Maps

I have integrated Markerclusterer with Google Maps in a similar way to the example provided. Here is my code snippet: var map = new google.maps.Map(document.getElementById("map"), options); var markers = []; for (var i = 0; i < 100; i++) { var latLn ...

Issue with Webstorm not automatically updating changes made to JavaScript files

On my HTML page, I have included references to several JavaScript files such as: <script type="text/javascript" src="MyClass.js"></script> When debugging in WebStorm using a Python SimpleHTTPServer on Windows with Chrome, I am able to set bre ...

Elements that are added dynamically will not inherit the correct CSS styles, requiring JavaScript to style them properly

My <ul> element dynamically adds <li> elements, and I am attempting to make the first <li> wider at 63% while the others are at 60%. However, the first one is only getting a width of 60%. Any thoughts on why this might be happening? I ne ...

Set the title attribute according to the content of the <p> tag

Recently, I encountered a situation where I had numerous p tags with a specific class (let's call it .text). My task was to include the title attribute containing the text of each tag. For example: <p> Hello, world </p> This would resul ...

There seems to be an issue with the functionality of chrome.storage.local.set()

Struggling with Chrome storage handling, I have reviewed the newest documentation for Chrome storage and implemented the following code snippet (found within an AJAX call success function, where info.userName is a value fetched from my backend program): ch ...

Please convert the code to async/await format and modify the output structure as specified

const getWorkoutPlan = async (plan) => { let workoutPlan = {}; for (let day in plan) { workoutPlan[day] = await Promise.all( Object.keys(plan[day]).map(async (muscle) => { const query = format("select * from %I where id in (%L) ...

Tips for extracting information from an HTML table into a function using Google Apps Script

I am experiencing difficulties in coding this. I have attempted to use google.script.run but it's not working as expected. When I click the button "Approved," the data should be sent to a function, but I'm unsure of what steps to take next. I fee ...

I am trying to set up an AJAX call in my index.html to connect to a React endpoint, but instead of accessing the intended page, I am getting

I am attempting to execute an AJAX call in my static file index.html to a React endpoint, but instead of the desired response, I am seeing the React homepage. Here is the code snippet from my index.html: <div id="data"></div> <scr ...