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

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.

Related

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.

How to reduce amap Array.prototype.some() calls?

I have a service managing data fetched from a SocketIO API such as
export class DataService {
private data: SomeData[];
// ...
getData(): Observable<SomeData[]> {
// distinctUntilChanged was used to limit Observable to only emits when data changes
// but it does not seem to change much things...
return this.data.pipe(distinctUntilChanged());
}
// ...
}
and a component calling this service to do
this.banana$ = combineLatest([
someFnToRequestANetworkObject(),
DataService.getData()
]).pipe(
map(([network, data]) => network && data.some(_data=> _data.ip === network.ip))
);
The thing is that each time one of the Observable handled within combineLatest gets emitted, I get to call Array.prototype.some() function. Which I don't want to.
How could I optimize this code so that I don't call some too often ?
One thing to note about distinctUntilChanged() operator is it affects only the subscription. So the operators between the source observable and the subscription are still run. As workaround you could manually check if data has changed between emissions. Try the following
let oldData: any;
this.banana$ = combineLatest([
someFnToRequestANetworkObject(),
DataService.getData()
]).pipe(
map(([network, data]) => {
if (!oldData || oldData !== data) {
oldData = data;
return (network && data.some(_data=> _data.ip === network.ip));
}
return false; // <-- return what you wish when `data` hasn't changed
})
);
I think your hunch about distinctUntilChanged not working is correct.
By default, this operator uses an equality check to determine if two values are the same.
However, this will (in most cases) not work properly if the objects being compared aren't simple scalar values like numbers or booleans.
As it turns out, you can provide your own comparator function as the first argument to distinctUntilChanged.
This will be called on "successive" elements to determine if the new element is different from the most recent one.
The exact definition of this comparator function depends on what your SomeData class/interface looks like, along with what it means for an array of SomeData inhabitants to be the same as another one.
But, as an example, let's say that SomeData looks like this:
interface SomeData {
id: string;
name: string;
age: number;
}
and that SomeData inhabitants are the same if they have the same id.
Furthermore, let's suppose that two arrays of SomeDatas are the same if they contain exactly the same SomeData elements.
Then our comparator function might look like:
function eqSomeDataArrays(sds1: SomeData[], sds2: SomeData[]): boolean {
// Two arrays of `SomeData` inhabitants are the same if...
// ...they contain the same number of elements...
return sds1.length === sds2.length
// ...which are "element-wise", the same (i.e. have the same `id`)
&& sds1.every((sd1, i) => sd1.id === sds2[i].id)
}
To round it all out, your getData method would now look like:
getData(): Observable<SomeData[]> {
return this.data.pipe(distinctUntilChanged(eqSomeDataArrays));
}
in the pipe, after map call, you could use shareReplay(1)
this.banana$ = combineLatest([
someFnToRequestANetworkObject(),
DataService.getData()])
.pipe(
map(([network, data]) => network && data.some(_data=> _data.ip === network.ip)),
shareReplay(1)
);

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.

MobX autorun firing just once

I am learning MobX and cannot understand why autorun is only firing once...
const {observable, autorun} = mobx;
class FilterStore {
#observable filters = {};
#observable items = [1,2,3];
}
const store = window.store = new FilterStore;
setInterval(() => {
store.items[0] = +new Date
}, 1000)
autorun(() => {
console.log(store.filters);
console.log(store.items);
console.log('----------------');
});
jsFiddle: https://jsfiddle.net/1vmtzn27/
This is a very simple setup, and the setInterval is changing the value of my observable array every second but autorun is not fired... any idea why?
...and the setInterval is changing the value of my observable array every second...
No, it isn't. It's changing the contents of the array, but not the observable MobX is watching, which is store.items itself. Changing that would look like this:
store.items = [+new Date];
Since you didn't access store.items[0] in the autorun callback, it isn't watched for changes. (console.log did access it, but not in a way MobX could see.)
If you do access store.items[0], it will be watched for changes; if you add to or remove from the array, you might want to access length explicitly as well:
autorun(() => {
store.filters;
store.items.length;
store.items.forEach(function() { } );
console.log('Update received');
});
Updated Fiddle

JS method calling from object value

I'm working on an emulator. The task at hand is an incoming request on a certain endpoint. The request may contain 1-4 options in the req.body.options. The basic design idea is that an object contains the options and the corresponding method calls (as some sort of a sub-router).
let dataActions = {
option1: optionMethod(param1, param2),
option2: optionMethod2(param1, param2),
option3: optionMethod3(params),
option4: optionMethod4(params)
}
for (key in req.body.options) {
...
}
The for...in should fire the methods (decoupled in other files) when it finds matching in the request with the dataActions keys. Is there a semantical way, or a detailed design pattern to make this work?
The problem is that you already fire the methods yourself.
let dataActions = {
option1: optionMethod(param1, param2) // <-- this is a function call
}
Doing it this way you assign the result of optionMethod() to option1. The above is effectively shorthand for
let dataActions = {};
dataActions.option1 = optionMethod(param1, param2);
If that helps making it more obvious.
You don't want to call the methods immediately. You want to store them for later use. Either store them directly:
let dataActions = {
option1: optionMethod // <-- this is a function reference
}
...or store a function that calls them in some specific way:
let dataActions = {
option1: function () {
return optionMethod('some', 'parameters');
}
}
now you can use them at a separate time, for example like this
Object.keys(dataActions).filter(a => a in req.body.options).forEach(a => {
var optionMethod = dataActions[a];
optionMethod();
});

Categories

Resources