If I have a service containing an Observable and I have the following code...
this.collapse.newChild.subscribe(name => {
if (name === "helpArea") {
this.collapse.process("helpArea", true);
}
// I would like to detach now
});
Is it possible to detach without canceling the original subscription? If I call unsubscribe then when I call this.collapse.newChild.next("thing") it throws an error Error: object unsubscribed.
So I don't think unsubscribe is what I am looking for, so is there a way to detach a single observer?
As a work around for now I am using...
this.collapse.newChild.map(name => name === "helpArea").first().subscribe(...)
Peter answer is correct from a design/practice PoV, but beware also that you get this error because you unsubscribe the subject.
What you want to stop is the subscription instead:
var subscription = this.collapse.newChild.subscribe(...)
// NOT this
// this.collapse.newChild.unsubscribe()
// this
subscription.unsuscribe()
This will kill/stop not the subject, but only your current subscription to it.
I don't think your "workaround" is at all bad. It even seems idiomatic: the way that Rx wants you to do it. But you can write it a bit shorter, using the form of first that takes a predicate.
this.collapse.newChild
.first(name => name === "helpArea")
.subscribe(...);
In general, the mindset to get into with Rx is to as much as possible use combinators to manipulate streams to produce exactly the values that you need, and then subscribe with a simple handler that applies the final, effectful, step: updating the UI, calling a service etc. This will lead to cleaner, more expressive code, that is easier to read and easier to re-use.
Another nice thing about this approach for your example is that there is no need to unsubscribe. RX will do that for you after the first value because it knows there won't be more. So it's much cleaner than doing it yourself.
Calling unsubscribe() on a Subscription object unsubscribes only this particular observer. So a question is how did you unsubscribed at the first place because using first() sends complete notification which causes unsubscription anyway.
Btw, you can always unsubscribe inside your own subscriber's callback:
var subscription = this.collapse.newChild.subscribe(name => {
if (name === "helpArea") {
this.collapse.process("helpArea", true);
}
subscription.unsubscribe();
});
Related
What is the difference between autorun and reaction? I'm looking for a full explanation that helps to decide when to use which. Is it possible that reaction can even trigger while autorun does not? How they are related to each other?
Are these both the same?
autorun(() => {
localStorage.setItem("current_order", JSON.stringify(this.currentOrder))
})
reaction(
() => JSON.stringify(this.currentOrder),
(json) => {
localStorage.setItem("current_order", json)
}
)
As far as I know, the first parameter of reaction will evaluate and check for changes, so it is desired for computed values, but shouldn't autorun just work fine if this.currentOrder is observable? Will JSON.stringify(this.currentOrder) in reaction run, even when this.currentOrder is observable and has not changed?
autorun runs immediately after it was initialised, reaction runs only after the data expression has changed after initialisation.
Also, because reaction runs only when data expression changes you can use other observables in the effect function, which would not work for autorun because it subscribes for all observables you use inside of it, which means reaction gives more fine grained control.
EDIT:
Adding an example to make it more clear
Consider this example, you have two observables A and B, and want to do something with B when A changes, but don't want to do anything when B changes. If you just make autorun it will run when any of the observables changes, so it's not what you want. But with reaction it's possible to only react to the changes of A (by only using A in expression function).
MobX works like a spreadsheet, everything is derived from state. Even effects with things like autorun.
The problem for me is that it's unclear when to run an effect based on some event firing, rather than based on a state-change (using autorun or something like that).
For instance let's say I need to fetch some milk (an action that causes an HTTP request - hence an effect) after 3 specific conditions are met (derived from state). I can use when to run this side-effect:
when(
() => {
if (
self.isOnConfirmationPage &&
paymentStore.successfullySubscribed &&
calendarStore.isFriday
) {
return true;
}
return false;
},
() => self.fetchMilk(),
);
A new requirement comes in asking me to add some logging. Specifically I need to call a track() service (another HTTP request hence another effect), but this track() service depends on the data returned by the fetchMilk() service.
Now I could simply add .then() to my fetchMilk:
self.fetchMilk().then(milkResponse => self.track(
milkResponse.user,
milkResponse.order
))
Going back to the title of my question, this to me is "reacting based on an event" - the event in this case being the response by the fetchMilk() service.
What if I simply react based on the state-change rather than the event?
This means track() needs to be also placed in a reaction, and since it depends on the response of fetchMilk() I can simply store this in a MobX store and react upon it:
when(
() => {
if (
self.milk.user &&
self.milk.order
) {
return true;
}
return false;
},
() => self.track(self.milk.user, self.milk.order),
);
Note that instead of using a "promise-based" flow for handling async behavior, I'm simply reacting based on values changing.
My question: which of these two options should I use? What are the benefits/cons of each approach? Is it safe to model async behavior using the 2nd approach?
I would go for the promise.then() version, simply because it is easier to follow the code flow.
There is no rule that says that everything in the app must be set up so that all code is driven by mobx actions and reactions.
When you want a change in the app to be broadcasted to the world, then you enter the mobx world.
When you think about it, mobx is just like an event dispatcher, but the event dispatch code (subscriptions and notifications) are hidden behind mobx magic.
So the best option in my experience is to do all the async work you need to do, and then notify mobx of the changes.
If you take a look at the official documentation it has an example of using asynchronous code, which does exactly that.
I am using angular 8.
There is one auto-complete input and if it's value changes I have to make API call and load new suggestions for this input.
//In Template
<autocomplate [suggestions]="suggestions" (filterChange)="filterChange($event)"></autocomplate>
//In Component
filterChange(e) {
console.log(e)
this.loadSubscriptions(e ? { 'filterItem.name': e } : {})
}
loadSubscriptions(params) {
if (this.suggestionsSubscriber) this.suggestionsSubscriber.unsubscribe()
this.suggestionsSubscriber = this.suggestionsService.loadData(params).subscribe(
data => this.suggestions = data
})
}
Everything works fine, but the problem is when user types fast application makes to many requests.
Can I somehow delay requests if user types fast? for example, while the user is typing don't make API calls on every change, and if the user stops typing then make API call.
Or if you have a better way to solve this problem, please share.
Use RXJS denounceTime operator. Simply chain it to your Observable.
Whenever debounceTime receives an event, it waits a designated amount of time to see if another event comes down the pipe. If it does, it restarts its timer. When enough time has passed without another event streaming in, it emits the latest event.
I would suggest you to use throttle or debounce. You can write your own implementation for those or use library such as lodash.
Debounce using latest Rxjs can be a work around. Please see below for implementation.
Angular and debounce
I also had a same problem, so i put my code inside setTimeout as below
filterChange(e) {
console.log(e)
setTimeout(()=>{
this.loadSubscriptions(e ? { 'filterItem.name': e } : {})
},2000);
}
Now if you type very fast then it will not call the loadSubscriptions at that time. it will call after 2 sec.
You can configure the time according to your choice.
I hope This will helps you.
exports.addNewValue = functions.database.ref('/path')
.onWrite(event => {
event.data.adminRef.update({"timeSnapshot":Date.now()})})
It appears that Date.now() causes an infinite loop in the function because the following does not:
exports.addNewValue = functions.database.ref('/path')
.onWrite(event => {
event.data.adminRef.update({"timeSnapshot":"newString"})})
How do I fix this?
If you write back to the same location in the database that was previously changed, you can expect this sequence of events:
Function is triggered with the first change from the client
Function writes back to the database
Function is triggered a second time because of the write during step #2
All writes to the database that match the filter path, even those from within the same function, will trigger the function.
In step 3, you need a strategy to figure out if the second function invocation should result in yet another write back to the database. If it does not require another write, the function should return early so that it won't trigger another write. Typically you look at the data in the event passed to the function and figure out if it was already modified the first time. This could involve looking to see if some flag is set in the database, or if the data you modified does not need any more changes.
Many of the code samples provided by the Firebase team do this. In particular, look at text moderation. Also, there is a video that describes the problem and a possible solution. In the end, you're responsible for coming up with the strategy that meets your needs.
I think the following should work fine :-
exports.addNewValue = functions.database.ref('/path/timeSnapshot')
.onWrite(event => { event.data.adminRef.set(Date.now()) })
The logic behind the above is that when you put a trigger function on a higher node (such as /path in your case), then the function would be fired each time a change is made to any of its child nodes (/timestamp in your case) - hence, the infinite loop.
Therefore, as a general practice, for efficiency as well as cost effectiveness, make sure that your trigger function has the lowest possible path node. Flatting out your data really helps in this as well.
If you arrived here having problems with querying try using .once('value') ... it will mean that you only look at the reference point once ... i.e.
ref.orderByChild("isLive").equalTo(true).once("value" , function(snapshot) {
instead of
ref.orderByChild("isLive").equalTo(true).on("value", function(snapshot) {
as the second will have you listening all the time, and when data changes at the ref, the listener will receive the changes and run the code inside your block again
I am writing a webpage with the following structure:
One section (table A) depends on another section (table B);
Another section (table B) has elements that require recalculation on each update. The calculation is handled by external tools, and will cause an event when finished.
In order to guarantee correctness, the table need to be updated only after the other table is fully updated (i.e., done with computation). However, I don't know how to effectively achieve this, and I could not find any wait facility within JavaScript.
For now, I am using the following method:
Declare a global variable updated and make it false;
After the first table received input, I make an empty while loop until updated is true;
Add an listener, once the calculation is done and the event received, set updated to true.
This seems unintuitive to me but I cannot think of any other way of doing it. Is there any good ways to do this?
Thanks for any inputs!
In 2022, it's useful to have an event listener that fires off a Promise (which can be used in promise-chains, or async/await code). A clean way to make one:
function getPromiseFromEvent(item, event) {
return new Promise((resolve) => {
const listener = () => {
item.removeEventListener(event, listener);
resolve();
}
item.addEventListener(event, listener);
})
}
async function waitForButtonClick() {
const div = document.querySelector("div")
const button = document.querySelector("button")
div.innerText = "Waiting for you to press the button"
await getPromiseFromEvent(button, "click")
div.innerText = "The button was pressed!"
}
waitForButtonClick()
<button>ClickMe</button>
<div></div>
Add an listener, once the calculation is done and the event received, set updated to true.
Instead of setting updated to true, and then waiting for updated to be true- just do whatever you want to do in the listener.
myEventBus.addListener(function () {
// do whatever
updateTable();
alert('table updated!');
});
Doing empty while loops is a bad idea. Not only do you burn CPU cycles, but Javacript is single threaded so you will loop forever without giving anyone a chance to change the variable.
What you can do is rewrite the table that has other people depending on it to "fire an event itself". There are many ways to do this, but basicaly you just want it to call a "continuation' function instead of blindily returning. This function can be predefined or you can pass it as a parameter somewhere.
//this is just illustrative
//Your actual code will be probably very different from this.
function update_part(){
//do something
signal_finished_part()
}
var parts_done = 0;
function signal_finished_part(){
parts_done ++;
if(parts_done >= 5){
signal_all_parts_done();
}
}
function signal_all_parts_done()
{
//do something to table A
}
You could write a callback function for whatever triggers the update. To avoid messy callbacks, you could use promises too, and update parts of the table depending on the data retrieved in the update operation. Open to suggestions.