baconjs / bacon.js

Functional reactive programming library for TypeScript and JavaScript

Home Page:https://baconjs.github.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Unexpected updates from combineTemplate and bulk model value changes

andpor opened this issue · comments

I have the following code that utilizes Bacon.Model properties and lenses. There is one top level Bacon.Model aka profileModel which represents the entire app state. Then two individual lenses (author and product) are created onto this which are aggregated using combineTemplate.

After the setup is complete, the entire model is changed using a single set() call. The data structure in the new model is the same as in original but the values are different.

What is very interesting and unexpected are the values received from a basic stream created using combineTemplate. Essentially I see two updates, <old product, new author> , <new product, new author> . Why is that?

When I adjust the combine template so that the properties in it are not raw lenses but rather a stream created by sampling the lens value using the lens value change stream I get only a single update , which is what I totally expect i.e. <new product, new author>

I do not understand why I get multiple callback using a basic combineTemplate on raw lenses. Here the fiddle

`var profileModel = new Bacon.Model({
"submitter": "KK440145",
"product": {
"name": "alert",
"code": "ALERTJ"
},
"author": {
"id": "WN77654"
}
});
profileModel.onValue(value => console.log("profile value is :",value));

var author = profileModel.lens("author");
var product = profileModel.lens("product");

Bacon.combineTemplate({product,author}).onValue(value => {
console.log(from basic template: ${value.product.name}, ${value.author.id});
});

Bacon.combineTemplate({
author: author.sampledBy(author.toEventStream()),
product: product.sampledBy(product.toEventStream())
}).onValue(value => {
console.log(from modified template: ${value.product.name}, ${value.author.id});
});

profileModel.set({
"submitter": "YTHE8888",
"product": {
"name": "focus",
"code": "FOCUSJ"
},
"author": {
"id": "KJX776544"
}
})
profileModel.set({
"submitter": "YU7655554",
"author": {
"id": "AP77654"
},
"product": {
"name": "industry",
"code": "FOCUSJ"
}
})

author.set({ id: "ZZTOP"})`

and the output:

profile value is : Object {submitter: "KK440145", product: Object, author: Object}
from basic template: alert, WN77654
from modified template: alert, WN77654
profile value is : Object {submitter: "YTHE8888", product: Object, author: Object}
from basic template: alert, KJX776544
from modified template: focus, KJX776544
from basic template: focus, KJX776544
profile value is : Object {submitter: "YU7655554", author: Object, product: Object}
from basic template: focus, AP77654
from modified template: industry, AP77654
from basic template: industry, AP77654
from basic template: industry, ZZTOP
from modified template: industry, ZZTOP
profile value is : Object {submitter: "YU7655554", author: Object, product: Object}

The bacon.model library is largely abandonware as of now. I'm not using it myself and haven't seen much interest in the developer community to take over. Just added a "not maintained" notice on the README.

As to the issue itself I haven't looked into it any deeper yet. Seems like Bacon.Model defeats Bacon's glitch prevention logic somehow.

IMHO you should look into using Bacon.update instead, as in

profileUpdateE // updates to the whole "profile" object
authorUpdateE // updates to the "author" object
profileP = Bacon.update({},
  profileUpdateE, (oldProfile, newProfile) => newProfile,
  authorUpdateE, (oldProfile, newAuthor) => { 
    let newProfile = R.clone(oldProfile)
    newProfile.author=newAuthor
    return newProfile 
  }
)

And instead of Ramda.clone you could use the partial.lenses library. Anyway, this is how I roll nowadays, in general :)

Can't use any other aggregation because the values in the template represent the paramaters , which can tick individually or in any combination. Listing them all inside update() is going to kill me unless there is some other way that I am not seeing...let's say for some cases I am looking at 8 different params that are part of the monitored template...which are then checked for integrity before a data call is sent.

Bacon.Model has been great because that is the only simple object I can create in Bacon that has the value retrievable via get()...is there any replacement for this you can suggest? There is no way to just create a property in bacon.js outright afaik...no?

Also Bacon.Model and the lens can be subscribed to individually which is great...not sure if partial.lenses integrates this tightly into Bacon...

You can add a Bus where you push (lens, value) pairs from difrent sources, then use this in Bacon update to update the current value in a generic way. You can use the same lens in combination with property.map to subscribe to the part that the lens represents.

Bacon.update is in fact a simple yet powerful way to make a Property. Why would you want to grab the value imperatively when you can attach a listener and get notified on all updates?

Because the model is created up front, has values that need to be queried for by some logic . Other logic is subscription based and I do in fact subscribe with onValue listener ..

Can you perhaps give an example where you could create an combineTemplate equivalent with update when combineTemplate has say 10 lenses and I am interested in receiving an value change callback when any of them tick and the handler should be common i.e. take all 10 lens values as param i.e. have access to their latest value...

combineTemplate({
lens1 : lens1,
....
lens10 : lens10})

Also not clear on the property.map subscription you had mentioned...

If I don;t use Bacon.Model lenses how would I know that a part of the model has changed?