Merging unknown number of observables with RxJS - javascript

I am currently trying to create a small project to demo Reactive Programming with RxJS. The goal is to show my colleagues that this thing is out there, and it is worth looking into. I'm not experienced with the framework, so that makes things complicated.
I am trying to expand another demo of mine to utilize RxJS.
It is not a very complicated demo, basically I can add any number of small forms, which result in a number calculated by a small formula, and there is a button, which sums up the values of all the forms.
Getting the formula calculate inside the forms is easy, but I figured I could go further.
I want to have the sum operation done automatically through a merged observable.
The only solution I figured out is something along these lines:
//dummy observables
var s1 = Rx.Observable.Interval(100);
var s2 = Rx.Observable.Interval(200);
//Combine the observables
var m = s1.combineLatest(s2, function(x,y){return x+y});
//Subscribe to the combined observable
var sub = m.subscribe(function(x){console.log(x)});
//A new observable is created
var s3 = Rx.Observable.Interval(300);
//Now I need to update all my subscriptions, wich is a pain.
m = m.combine(s3, function(x,y){return x+y});
sub.dispose();
sub=m.subscribe(function(x){console.log(x)});
I figured I could get another observable to notify my subscriptions to update themselves - as knowing how all my subscribers work would render the whole architecture useless, but this sounds like an overkill for a task like this, and I don't just mean the demo, I can't really imagine to have an "every day" real world example where an architecture like this would make things cleaner than just watching for any change, and getting the calculated values "actively" from my forms.
I'd probably do the active getting, and summing of values inside the module handling the forms, and have the "m" observable for the outside world to subscribe to, pushing my values into it from inside the module.
Would this be a correct approach? I'd think yes, because they are owned by my module, I'm supposed to have full control over what is happening to them, but I'm really interested in what more experienced people think about this.

I know I'm late to this party, but I just had this same exact question, and could not find any suitable answer. I figured out how to do this in the context of my application, so unless this is still an open issue for you, I'll post the answer here for posterity.
To set up my problem, I have an Angular form with a number of controls. Anytime the validation state changes on any one of the controls, I need to do something. However, controls can be dynamically added to the form later, and I still need to listen for validation changes on those individual controls as well.
I'll try to alter my approach a little to fit the question's context. Basically, yes the right approach here is to have an outer Observable signal when to have the inner Observables update their subscriptions. This is acceptable architecture and frankly is one of the reasons Reactive Extensions is so powerful. Here's how I did it using RxJS 6.2.2 and TypeScript 3.0.1. Note that at the time this question was asked, or previously answered, this method might not have been available.
import { map, mergeMap, delay } from 'rxjs/operators';
import { interval, Subject, Observable } from 'rxjs';
// utility function to create sample observables
const createObs = (time: number, i: string) => interval(time).pipe(map(val => `${i}: ${val}`))
// create static sample observables.
// these could represent the value changes of known form elements
const obsA = createObs(1000, 'A'); // emit every 1 second
const obsB = createObs(2500, 'B'); // emit every 2.5 seconds
// create a Subject which will represent dynamically adding a new stream of form values to the calculation
const formChanges = new Subject<Observable<string>>();
// this will hold the accumulated results
const results: string[] = [];
// subscribe to the subject
// each time any of the inner observables emits a new value, this will log the latest value of all existing inner observables
formChanges.pipe(
mergeMap(innerObs => innerObs, (outerValue, innerValue, outerIndex, innerIndex) => {
results[outerIndex] = innerValue;
return results;
})
).subscribe(console.log);
// emit the known form value change streams
formChanges.next(obsA);
formChanges.next(obsB);
// this will represent adding some other stream of values to the calculation later
formChanges.next(createObs(1750, 'C').pipe(delay(5000))) // after 5 seconds, emit every 1.75 seconds
// Output
// [ 'A: 0' ]
// [ 'A: 1' ]
// [ 'A: 1', 'B: 0' ]
// [ 'A: 2', 'B: 0' ]
// [ 'A: 3', 'B: 0' ]
// [ 'A: 3', 'B: 1' ]
// [ 'A: 4', 'B: 1' ]
// [ 'A: 5', 'B: 1' ]
// [ 'A: 5', 'B: 1', 'C: 0' ]
// [ 'A: 6', 'B: 1', 'C: 0' ]
// [ 'A: 6', 'B: 2', 'C: 0' ]
// [ 'A: 7', 'B: 2', 'C: 0' ]
// [ 'A: 7', 'B: 2', 'C: 1' ]
// [ 'A: 8', 'B: 2', 'C: 1' ]

I don't think you will find an operator that will directly do what you need.
There is nothing wrong with crafting your own operator though:
var source = //An observable of observables of form data
Observable.prototype.combineLatestObservable = function(resultSelector) {
var source = this;
return Rx.Observable.create(function(obs) {
var disposable = new Rx.SerialDisposable();
var sources= [];
return source.subscribe(
function(x) {
//Update the set of observables
sources.push(x);
//This will dispose of the previous subscription first
//then subscribe to the new set.
disposable.seDisposable(Rx.Observable.combineLatest(sources, resultSelector)
.subscribe(obs));
},
function(e) { obs.onError(e); },
function() { obs.onCompleted(); });
}).share();
}
Or if you wanted to do it with operators:
//Have to use arguments since we don't know how many values we will have
function sums() {
var sum = 0;
for (var i = 0, len = arguments.length; i < len; ++i) {
sum += arguments[i];
}
return sum;
}
source
//Capture the latest set of Observables
.scan([], function(acc, x) {
acc.push(x);
return acc;
})
//Dispose of the previous set and subscribe to the new set
.flatMapLatest(function(arr) {
return Observable.combineLatest(arr, sums);
})
//Don't know how many subscribers you have but probably want to keep from
//recreating this stream for each
.share();

If you look at the source of combineLatest you can see, that it expects a variable number of Observables, not only a single one. With that in mind you could do something like this:
// Whatever Observables you want...
const a = Rx.Observable.of(1,2,3)
const b = Rx.Observable.interval(100).take(5)
const c = Rx.Observable.of(2)
Rx.Observable.combineLatest(a, b, c) // notice, I didn't define a resultSelector
.do(numbers => console.log(numbers)) // you get 3 element arrays
.map(numbers => numbers.reduce((sum, n) => sum + n, 0))
.subscribe(sum => console.log(sum))
Output of this with my example will be:
[3, 0, 2] // array printed in the do block
5 // the sum of the array printed from the subscription
[3, 1, 2]
6
[3, 2, 2]
7
[3, 3, 2]
8
[3, 4, 2]
9
If you would like to call this with an array of Observables you could use Function#apply to do this:
Rx.Observable.combineLatest.apply(Rx.Observable, [a, b, c])
Hope I didn't misunderstood what you are asking

I am using this snippet to merge unknown number of observables -
someArrayOfIdsToPerformRequest: [];
this.isLoading = true;
const mergedRequests: Array<Promise<JSON>> = [];
this.someArrayOfIdsToPerformRequest.forEach((item) => {
mergedRequests.push(this.postsService.remove({id: item.id}) //returns observable
.toPromise());
});
Promise.all(mergedRequests)
.then(() => {
this.isLoading = false;
this.loadPosts();
});
Hope this helps someone!

Related

How to combine JS Objects with duplicate key/values

sorry if this is a easy question, I am just having a hard time trying to figure out how I would tackle this problem.
For example, I have 2 Objects as below:
cont oldCar = {
model: 'Honda',
notes: {
id: 1,
timestamp: 2000,
text: 'is old'
}
}
cont oldCar = {
model: 'Toyota',
notes: {
id: 1,
timestamp: 4000,
text: 'is new'
}
}
I want to try and combine the above two objects. I know they have same key's so I wanted to merge the values of each key if they are the same. Such as:
mode: 'Honda / Toyota'
I tried the following:
let merged = {...obj1, ...obj2};
But this will merge both objects but it only retains the values from the right object. I was trying to do a for loop and add check if the key is same in both objects then combine the values together but I keep getting lost and it is hard to visualise. If someone could help me understand how i can create an for loop to start the comparison that would help me in completing the rest.
To do this merge, perhaps you could do some array-reduce, it will also work with a list of unspecific size:
let array = Array(oldCar1,oldCar2)
let result = array.reduce((a,b)=> {
let r = Object.assign({},a)
r.notes = Object.assign({},r.notes)
if (a.model != b.model) {
r["model"] = a.model + " / " + b.model;
}
if (a.notes.text != b.notes.text) {
r.notes.text = a.notes.text + " / " + b.notes.text;
}
// ...
return r;
})
What exactly do you want to achieve? Is it only the merging of model prop or something else?
Do you have more than two objects, is the amount of objects dynamic? If there are only two objects you can do that without any loops.
const merged = {
model: `${firstCar.model} / ${secondCar.model}`,
// etc...
};
But as I said before - if the amount of objects is not constant then you'd need a map that would:
go through each car
try and find a match by ID from other cars
if there's a match return a merged result, if there's no match return the object as it is
Let me know what exactly are your needs here.

Get parent array from child array

In the code below I can easily enter into a subarray within the array with current = current[0].
var root = [
[
'Value 1',
]
];
var current = root;
current = current[0];
current.push('Value 2');
document.write(JSON.stringify(root));
How can I do the reverse and go up a level? In this example, current would become:
[['Value 1', 'Value 2']]
The only way I can think of doing this would be looping, but duplicates would be very probably and problematic. All I've found online is looping, which won't work as a mention in the last sentence.
EDIT
I'm looping through a large string and converting it to a tree. Certain characters will require a indent in the tree or a separate branch, while others will require an outdent and return to the parent function. I've achieved the indents by having a current variable just enter a new subarray as suggested here. I just need a way to exit with a different character.
You can't.
Arrays have no concept of a "parent" or anything. They don't care where their references are being held. However, if you really really need to, you can implement the concept of a "parent" yourself using a simple recursive function setting properties of the arrays - see below:
var root = [
[
'Value 1',
]
];
function setParents(root) {
for(var i = 0; i < root.length; i++) {
if(Array.isArray(root[i])) {
root[i].parent = root;
setParents(root[i]);
}
}
}
setParents(root);
var current = root;
current = current[0];
current.push('Value 2');
document.write(JSON.stringify(root));
current = current.parent;
console.log(current);
This makes use of the fact that array properties don't show up when logged or serialized as JSON, only their elements. So they're kind of "hidden" in a sense. It's a bit hacky but could work for you.
However, I recommend you simply avoid overwriting current - there's not really a great need to use it like a cursor traversing some hierarchy structure.
Simply pushing BEFORE setting current variable will work.
var root = [
[
'Value 1',
]
];
var current = root;
root.push("AAA");
current = current[0];
current.push('Value 2');
document.write(JSON.stringify(root));

Can I refresh the values of (an array of) objects as the variable they refer to changes?

I am trying to make a generator of simple sentences by combining data from two arrays. One array has the nouns/subjects and the other the verbs/actions.
Obviously, some nouns are singular and some plural. To make this work, I make the nouns objects (and a class), eg: { txt: "Jason", snglr: true }, { txt: "The kids" }.
Then, I create a function that forms the verb according to whether the noun is singular or plural (snglr: true or false/undefined) and apply it as a method of the noun class.
I call the method from within the string of the verb/action, as in: { txt: `${curr_noun.verb("go", "goes")} to music class.`}
My problem is that all the verbs get their values from the first noun used. When I change the noun, the verbs still refer to the plural or singular form called by the first noun.
Is there a way to refresh the actions objects, so that each time their values refer to the current noun?
(Or if I'm totally missing an easier way, can you please let me know?)
Thanks. Here is the whole code:
class noun {
constructor(text, singular) {
this.txt = text; // The subject of the sentence
this.snglr = singular; // Whether the subject is singular (true) or plural (undefined/false)
this.verb = function (plur, sing) { //this function is called from within the string containing the verb (see "actions" array, below)
if (sing == undefined) { // for regular verbs, we call it with one argument, eg. .verb("walk"), it creates "walks" by adding "s."
sing = plur + "s";
}
if (this.snglr) { // for irregular verbs, we call it with two arguments, eg. .verb("go", "goes")
return sing;
} else {
return plur;
}
}
}
}
var curr_noun = {};
var nouns = [new noun("Jason", true), new noun("The kids")];
curr_noun = nouns[0]; // We pick "Jason" as the sentence's subject.
var actions = [
{ txt: `${curr_noun.verb("go", "goes")} to music class.`},
{ txt: `${curr_noun.verb("visit")} London.`}
];
curr_action = actions[1];
console.log(`${curr_noun.txt} ${curr_action.txt}`);
// All good, so far.
// My problem is that the above values of actions[0].txt and actions[1].txt are given based on Jason.
// When I later change the curr_noun to "The kids," I still get singular verb forms.
curr_noun = nouns[1];
curr_action = actions[0];
curr_noun.verb();
console.log(`${curr_noun.txt} ${curr_action.txt}`);
I think we could simplify this code a bit. You could create a function to create the sentences. This function would take a noun object and verb object and the grammatical object which is a string.
This way we can remove the class and reduce the number of lines that we write. With this approach, we can also ensure the nouns and verbs match in terms of plurality. Here is an example:
const nouns = [
{
text: 'Jason',
isSingular: true,
},
{
text: 'The kids',
isSingular: false,
},
];
// in grammar, the noun at the end of the sentence is called object
const verbs = [
{
// "to" is actually part of the verb here(prepositional verb)
singular: 'goes to',
plural: 'go to',
},
{
singular: 'visits',
plural: 'visit',
},
];
// in grammar, the noun at the end of the sentence is called object
const objects = ['music class.', 'London.'];
const createSentence = (noun, verb, object) => {
const myVerb = noun.isSingular === true ? verb.singular : verb.plural;
return `${noun.text} ${myVerb} ${object}`;
};
console.log(createSentence(nouns[0], verbs[1], objects[1]));
console.log(createSentence(nouns[1], verbs[0], objects[0]));
Note: I am not saying that this is how it should be done, but just pointing out where the problem is.
You define your var actions based on curr_noun = nouns[1];, i.e. "Jason", here:
var actions = [
{ txt: `${curr_noun.verb("go", "goes")} to music class.` },
{ txt: `${curr_noun.verb("visit")} London.` }
];
This never changes. So when referring to actions[0] later, they are still based on "Jason".Try to console.log(this.txt) inside the .verb-method to see where it goes wrong.
When reassigning them again to "The kids" before logging, it works fine:
curr_noun = nouns[1];
var actions = [
{ txt: `${curr_noun.verb("go", "goes")} to music class.` },
{ txt: `${curr_noun.verb("visit")} London.` }
];
curr_action = actions[0];
console.log(`${curr_noun.txt} ${curr_action.txt}`);
// -> The kids go to music class.

javascript sort, Compare 3 different types and sort in order

I have an array of objects. each object has a "day type"; morning, afternoon, evening. They come in random order each time I get the list due to async differences... But I need to display them in following order: morning, afternoon, evening.
If I use array.sort() they will come out in order: afternoon, evening, morning. because it sorts alphabetically right?
So... I need a compare function. but I cant figure out how to make it since all examples I have found only has two input. sort(a, b)......
day.shifts = randomOrder;
day.shifts.sort((a, b, c) => {
//some how compare all 3
}
I hope this is possible, and it is probably piece of cake.... Other wise I have thought of solutions like so
day.shifts = randomOrder;
const morning = [];
const afternoon = [];
const evening = [];
day.shifts.forEach(shift => {
if(shift.type === 'morning'){
morning.push(shift)
}
if(shift.type === 'afternoon'){
afternoone.push(shift)
}
if(shift.type === 'evening'){
evening.push(shift)
}
}
day.shifts = morning.concat(afternoon).concat(evening);
I would prefer a compare method if that is possible. Or at least something a bit more "pretty" than my split to separate arrays and the concatenate method...
the objects in the list looks like this:
{
type: 'morning',
name: 'some person',
id: 'person db index',
}
You can sort them like this:
const order = ["morning", "afternoon", "evening"];
day.shifts.sort((a, b) => order.indexOf(a.type) - order.indexOf(b.type));
That basically sorts the shifts after wich type comes first in the order array.
You could sort by type with an object for the order.
var types = { morning: 1, afternoon: 2, evening: 3 };
day.shifts.sort((a, b) => types[a.type] - types[b.type]);

Meteor sends entire array when only single element is updated

I have a collection that has a 10 x 10 2-d array field. When I update an element in this array, the entire field is sent back to me as changed.
Is there a better way to do the update part?
I'm new to noSQL so maybe I just need to rethink my database setup. This would be a little depressing as I love the simplicity of being able to map javascript objects directly to fields in a single collection. I, however, am not willing to lose ~500 bytes of overhead every time I update this thing.
Is there a way to force Meteor to update the client with more fine-grained changes? Or is this a limitation of how Meteor Livequery works?
Thanks!
The problem is that DDP only supports changed messages at the top-level field granularity. With your current structure, any change to a sub-field of squares will result in the entire squares field being transmitted to the client.
One alternative is to store each element of the 2D array as a separate, top-level field. Below is a complete working example using a - character to separate the elements:
var pool2squares = function(pool) {
var squares = [];
_.each(pool, function(value, key) {
var parts = key.split('-');
if (parts[0] === 'squares') {
var i = Number(parts[1]);
var j = Number(parts[2]);
if (squares[i] == null)
squares[i] = [];
squares[i][j] = value;
}
});
return squares;
};
Pools = new Mongo.Collection('pools', {
transform: function(doc) {
return _.extend(doc, {squares: pool2squares(doc)});
}
});
Meteor.startup(function() {
Pools.insert({
'squares-0-0': 'a',
'squares-0-1': 'b',
'squares-1-0': 'c',
'squares-1-1': 'd'
});
console.log(Pools.findOne().squares);
});
Here we are using a collection transform to add a squares property to each Pool document whenever it's fetched. The above code will print:
[ [ 'a', 'b' ], [ 'c', 'd' ] ]
To update a Pool:
Pools.update(id, {$set: {'squares-3-4': 'hello'}});

Categories