How to avoid writing same http subscribe blocks for different components? - javascript

Let's assume that there is a service will be used for http request calls.And two different components(could be more than two) which will send same request by using same observables via this service.After getting result that should be assigned to global variable(Components have not relationship like parent-child or child-parent).Below I wrote same code block for all components.Is there any better way to write this function once and call by returning same value?
Service
getStudents() {
const requestUrl = this.apiUrl + 'students/';
return this.httpClient.get(requestUrl);
}
Component1
studentList:Student[]=[];
getStudents.subscribe((students:Student[])=>{
this.studentList=students;
//Some operations
})
Component2
studentList:Student[]=[];
getStudents.subscribe((students:Student[])=>{
//Some operations
this.studentList=students;
})

I'm not a fan of global state, but if you want to maintain the same list of students across components using global state, then that state may as well live in the service (Rather than existing in each component separately)
So, for example:
Service
studentList:Student[] = [];
setStudents(students:Student[]) {
this.studentList = students;
// Operations involved with setting students
}
updateStudents() {
const requestUrl = this.apiUrl + 'students/';
return this.httpClient.get(requestUrl).pipe(
tap(this.setStudents)
);
}
Component
ngOnInit(){
this.service.updateStudents().subscribe();
}

You can have an Observable inside your service,
studentsReceived:Subject = new Subject();
on success of getStundent() you can emit next value of studentsReceived.
Now you can subscribe to the studentsReceived inside your components, after the successful API call you will be notified of each of the subscribed components.
studentRecived.subscribe(data=>{ // do some code })
You must call this getStudent() on some higher component like AppComponent.

2 Important things here:
1) If you dont want to repeat the same block of code, then create a method in the service file,
and call it in the component. Something like this:
SERVICE:
makeSubcription(componentVariableName) {
this.yourObservable.subcribe(subscribedValue => {
componentVariableName = subscribedValue;
})
}
In your Component, you can do this:
yourComponentMethod() {
this.service.makeSubscription(this.studentLists);
}
************
2) If you dont want to make a service call too many times, what you can do is,
use Behavior Subject and try to store the values, so that you are subscribing to the observable and not the actual API call. Something like this:
private initialValuesForObservable: YourObjectModel = {}; // or null;
private observableSource: BehaviorSubject<YourObjectModel> =
new BehaviorSubject<YourObjectModel>(this.initialValuesForObservable);
public subscribableObservable: Observable<YourObjectModel> =
this.observableSource.asObservable();
setObservableValue(data: YourObjectModel) {
this.observableSource.next(data);
}
getObservableData() {
return this.subscribableObservable;
}
In your COMPONENT;
this.service.getObservableData().subscribe(latestObservableData => {
this.schoolList = latestObservableData;
});

Related

RXJS: How do I add new values to an ovservable after creation?

Im working through the basics of observables [here][1]
The examples here show Observasbles as functions with multiple returns e.g.
import { Observable } from 'rxjs';
const foo = new Observable(subscriber => {
console.log('Hello');
subscriber.next(42);
subscriber.next(100); // "return" another value
subscriber.next(200); // "return" yet another
});
foo.subscribe(x => {
console.log(x);
});
// outputs 42, 100, 200
Question: Is it possible to add new values to an observable AFTER it has been created e.g. something like this pseudo code:
import { Observable } from 'rxjs';
const foo = new Observable(subscriber => {
subscriber.next(200);
});
// this is pseudocode so wont work, but is the basis of this post.
...
foo.next(400);
...
foo.subscribe(x => {
console.log(x);
});
// is it possible to output 200 and 400 by calling next after instantiation?
Or do I need to use a Subject to do that?
You have to use Subject for this. According to documentation
Every Subject is an Observer. It is an object with the methods
next(v), error(e), and complete(). To feed a new value to the Subject,
just call next(theValue), and it will be multicasted to the Observers
registered to listen to the Subject.
Observables do not have the next method so you can not pass values to them.
You need to use Subject or BehaviorSubject. But BehaviorSubject has an initial value, so you can try to do something like this:
var obs = new rxjs.Observable((s) => {
s.next(42);
s.next(100);
});
obs.subscribe(a => console.log(a));
var sub = new rxjs.BehaviorSubject(45);
obs.subscribe(sub);
sub.next('new value');
sub.subscribe(a => console.log(a));
Every Subject is Observable, so you can easily convert Subject to Observable via yourSubject.asObservable().
NOTE: Don't forget to unsubscribe from observable in order to prevent memory leaks.

Detect when svelte store is not used anymore

I'm making a custom svelte store by wrapping around a svelte writable store.
I want to detect when that store is not subscribed by any component; when the subscription count is 0
My objective is to clear some heavy external resources (websockets) that were tied to the custom store when no one is using it.
Currently, I'm counting the subscriptions and unsubscriptions by wrapping around subscribe( ) method. It works as expected. But It looks like a nasty hack to me.
My question: Is there a standard / clean way to achieve this behavior in Svelte?
If not, can someone with more experience in Javascipt and svelte confirm whether this is legit?
Demo on : https://svelte.dev/repl/f4e24fb5c56f457a94bf9cf645955b9f?version=3.43.1
import { writable } from 'svelte/store';
// Instanciate the store
export let store = MakeStore();
// By design, I want a function that returns a custom svelte store
export function MakeStore(initialValue = null) {
const { subscribe, set, update } = writable(initialValue);
let subscribercount = 0;
let wsubscribe = function (run, callback) {
subscribercount++;
console.log("subscribercount++", subscribercount);
let wunsubscribe = subscribe(run, callback);
return () => {
subscribercount--;
console.log("subscribercount--", subscribercount);
if (subscribercount == 0) {
// -------------------------------
// Free up resources
// I want a clean way to get here
// -------------------------------
console.log("Cleaning up...");
}
return wunsubscribe();
}
}
// Some external calls here
let store = {
subscribe: wsubscribe,
set: newvalue => {
set(newvalue);
// Some external calls here
},
update: update
};
// Some external calls here
return store;
}
Yes, it's built into the store and documented here
from the docs
If a function is passed as the second argument, it will be called when the number of subscribers goes from zero to one (but not from one to two, etc). That function will be passed a set function which changes the value of the store. It must return a stop function that is called when the subscriber count goes from one to zero.
so you would do for example:
const count = writable(0, () => {
console.log('got a subscriber');
return () => console.log('no more subscribers');
});
Update 8 Feb 2023
Note that the above works for both readable and writable stores, in the case of derived stores where you would have the following code:
const count = derived(items, ($items, set) => {
console.log('got a subscriber to a derived store');
return () => console.log('no more subscribers to derived store');
});
Here it will log no more subscribers to derived store, when either the number of subscribers drops to 0 or when the original store changes (this is because this entire function ($items, set) => {...} runs again).
As of v3.55.1 there is no built in way to circumvent this.

Why does this getter method has been called several times in React?

I have a Store which will be provided to the component. In this Store file, there are several getter function. But I find only this getter function will be executed three times since this.rawMonthlyImpacts will be only changed once when the api get response from backend. I am so confused because other getter function in this file will be only executed once. During every execution, this.rawMonthlyImpacts is always same. Because this function is time-consuming, so I want to figure out why this happens. Hope you can give me some advice. Thanks!
get Impacts(){
const monthlyImpacts = new Map<string, Map<string, number>>();
if (this.rawMonthlyImpacts) {
this.rawMonthlyImpacts.forEach((impact) => {
if (impact.Impact > 0) {
const month = TimeConversion.fromTimestampToMonthString(impact.Month);
const tenantId = impact.TenantId;
const tenantImpact = impact.Impact;
if (!monthlyImpacts.has(month)) {
const tenantList = new Map<string, number>();
monthlyImpacts.set(month, tenantList.set(tenantId, tenantImpact));
} else {
const tenantWithImpactMap = monthlyImpacts.get(month);
if (!tenantWithImpactMap.has(tenantId)) {
tenantWithImpactMap.set(tenantId, tenantImpact);
} else {
tenantWithImpactMap.set(tenantId, tenantWithImpactMap.get(tenantId) + tenantImpact);
}
monthlyImpacts.set(month, tenantWithImpactMap);
}
}
});
}
return monthlyImpacts;
},
Update: I have find that there are other two functions use this.Impacts. If I remove these two functions, the getter function will only be executed only once. I think the getter function uses the cache to store data, so once the data is calculated for the first time, subsequent calls to the getter function should not be re-executed, only the value in the cache needs to be retrieved. So I am very confused about why this getter function will be executed 3 times.
getImpactedTenants(month: string): string[] {
return Array.from(this.Impacts.get(month).keys());
},
get overallMonthlyImpactedTenants(): Map<string, number> {
return new Map<string, number>(
Array.from(this.Impacts)?.map((monthEntries) => {
const month = monthEntries[0];
const impactedTenants = monthEntries[1].size;
return [month, impactedTenants];
})
);
}
Hard to tell what exactly is happening without more context, but remember that with a get function, every single time you reference that property (.Impacts in this case) the get function will be called.
Assuming that each impact stored in this.rawMonthlyImpacts which you loop through is an instance of the class with this getter, then as far as I'm aware, you are calling the get function each time you reference impact.Impacts, such as in the conditional:
if (impact.Impact > 0) {
I might be way off though; I'm unfamiliar with React and so my answer is based only on my experience with vanilla JS.

How to define a variable accessible from all methods of the class?

I'm new to JavaScript and probably trying to emulate Ruby with this. I use StimulusJS, but I think the question is applicable to JS in general.
My goal is to run a method (on button click) which would fetch and display all subcategories of the category from the button. The method/function would first fetch the data and then manipulate the DOM. I wanted to split these into two methods, but when I call the other method (displaySubcategories) from within the first one (getSubcategories) the value of event changes. Same thing with this in the fetch block - I need to assign it first to self to be be later able to related to object itself.
Is there a better way to do this? Like variables accessible to all methods (instance variables in Ruby)? Or I shouldn't be splitting these into two functions at all (not JS way)?
import {Controller} from "stimulus"
export default class extends Controller {
static targets = ["categoryId", "categoryLabel", "subcategoryList", "subcategoryHeader"]
getSubcategories() {
let category_id = event.target.firstElementChild.value
let url = "/api/categories/" + category_id + "/subcategories?levels=1"
let self = this
let e = event
fetch(url)
.then(response => response.json())
.then(json_response => {
self.displaySubcategories(json_response, e)
})
}
displaySubcategories(json_response, event) {
subcategories.forEach(function (subcategory) {
let category_label_copy = this.cloneLabel(current_label, subcategory)
current_label.classList.add("chosen")
subcategory_list.appendChild(category_label_copy)
}, this)
}
}
expenses#getSubcategories">

How to correctly add View value in Module via Controller?

Actually the whole question in the title, I have a button written in HTML, let's say this is my View, which returns a number:
export class View {
addItem() {
let plus = document.querySelector('.plus');
plus.addEventListener('click', () => {
let num = document.querySelector('.count').innerHTML;
return num;
})
}
}
Here is my Module with addNum function, which actually should add a number to the array:
export class Module {
constructor() {
this.num = [];
}
addNum(num){
this.num.push(num);
}
}
Heres the Controller:
class Controller {
constructor(view, module){
this.view = view;
this.module = module;
}
getNum(){
this.cart.addNum(this.view.addItem());
}
}
The problem is that when I call the getNum controller function, it works instantly, how can I wait for an event?
This can be naturally handled with RxJS observables. They are well-suited for such purposes, i.e. building reactive UIs and extensively used in Angular.
An observable is basically a stream of values that can be transformed and in the end, subscribed. It has a lot in common with promises which were suggested in another answer, but an observable results in a sequence of values, while a promise results in one value.
RxJS contains extensive functionality, including the support for DOM events. RxJS fromEvent (see a short tutorial) replaces addEventListener and creates a stream of click events that can be mapped to another value (form input value):
addItem() {
let plus = document.querySelector('.plus');
return Observable.fromEvent(plus, 'click')
.map((event) => {
return document.querySelector('.count').innerHTML;
});
}
An observable is subscribed in place where values should be received:
getNum(){
this.numSubscription = this.view.addItem().subscribe(num => {
this.module.addNum(num);
});
}
A stream of values can be stopped by unsubscribing from an observable, this.numSubscription.unsubscribe(). This will internally trigger removeEventListener.
Here's a demo.

Categories

Resources