Vuex two commits in same action does not trigger watch - vue.js

When I watch $store.stateA, the watch callback function for the following seems to not trigger.
stateA: false
muationA(state,val){
state.stateA = val
}
actionA({commit},val) {
commit('mutationA', true)
commit('mutationB', false) //take this out would trigger the watch callback
}
So what is the case here? the entire action callback need to finish before the watch functions are triggered?

As Nit mentioned, both mutations cancel each other since they are done synchronously. Read the "Reactivity in Depth" section of the doc for more info. In particular (emphasis mine):
In case you haven’t noticed yet, Vue performs DOM updates asynchronously. Whenever a data change is observed, it will open a queue and buffer all the data changes that happen in the same event loop. If the same watcher is triggered multiple times, it will be pushed into the queue only once. This buffered de-duplication is important in avoiding unnecessary calculations and DOM manipulations. Then, in the next event loop “tick”, Vue flushes the queue and performs the actual (already de-duped) work.
Other watcher solutions do work differently and are triggered right away when the value changes (e.g. Backbone.js). Vue does not work that way.

I wanted once to use a vuex variable as an event transmitter across components and needed to set a boolean variable to false and immediately to true afterward. I however saw that the watch in the component did not pick it up. The issue as mentioned in the other post is that they cancel each other because they are in the same event loop.
In order to make that work, I just had to separate their event loop by waiting for the Vue.nextTick() as follows:
// First operation
await Vue.nextTick();
// Second operation

Related

How do I hook into the blur event of the v-currency-field?

I'm writing a vue.js application with the v-currency-field package: https://www.npmjs.com/package/v-currency-field
When I read the documentation, I see nothing for a blur event (or any events at all): https://phiny1.github.io/v-currency-field/started.html#introduction
I'm getting around this by selecting the input inside the v-currency-field like this:
const holdbackAmountInput = document.querySelectorAll('[aria-label="Holdback amount"]')[0];
holdbackAmountInput.addEventListener('blur', this.holdbackAmountInputBlurHandler);
But is there not a better way to do this? It gets more complicated than the above code because I have to run it in the updated() hook and check if the holdbackAmountInput exists, and if it does, I have to assign it to a data object so that I have a reference to it when I have to remove the event listener when it's removed from the DOM or when the component is destroyed. Seems needlessly complicated for something that should be really simple.
Is there not a simpler way to do this?

Vue component loses input when asynchronous call updates another property

I have a component that has an <input> with a :value set to a certain property. The component has another property unrelated to this input, which can get updated asynchronously (AJAX call). Whenever you're typing inside the input and the asynchronous call finishes, updating the other property, your typed input is reset.
To recreate this problem I've created a jsfiddle using a setInterval to simulate the async call and increment the other passed property. Try typing in the input, it will get reset every second. If you're quick enough, you can tab out and cause the #change to trigger the actual update.
The question is: why is the update to the other prop invalidating/rerendering the component and how can I work around this?
Note that v-model="person.name" is not a valid solution here - I need to know the old and new value, which is why I'm using a manual :value/#change combo.
Edit: The updateName method also really only needs to be triggered when the user leaves the input field. This is because the code run inside it is relatively CPU intensive and only needs to run when the user is done with the input and leaves it (in my actual code, not the jsfiddle).
Edit2: Is there some way to not let it re-render the entire component, but only the relevant pieces?
Because the parent component is changing a property of the child component, it has to re-render (parts of) the child component. Since you are using #change, instead of #input, your changes are not saved yet to the reactive variable person.name, it only works if you click tab quick enough. One solution would be to change #change to #input (which better resembles v-model):
https://jsfiddle.net/mf67xq1e/
Another (better) option is to use v-model and use a watcher to retrieve both the old and the new value:
watch: {
// whenever person.name changes, this function will run
"person.name": function (newName, oldName) {
console.log("newName:", newName);
console.log("newName:", oldName);
}
}
https://jsfiddle.net/mf67xq1e/1/
EDIT:
As you mentioned you only need to trigger something when you blur/leave the input field, seperate the reactivity of the variable and the triggering of your other method (e.g. updating some other variable or something), in two seperate variables:
https://jsfiddle.net/ta9cgnx0/
EDIT 2: Cleaner option with v-model and a seperate call for your other trigger on blur:
https://jsfiddle.net/ay1g63u8/

Angular Subscription in component ngOnInit

I am learning the Angular 5+ and recently comes to the subject/subscription part, I see many tutorial would like to use the subscription in the certain way:
Declare the subscription in component
Subscribe it in ngOnInit via a service's subject or ngrx/store
Unsubscribe it in ngOnDestroy
However, I am not sure if we have to subscribe/unsubscribe every subscription in the component in ngOnInit and ngOnDestroy. For example, if my subscription will get updated through a button click event, which plan should I subscribe it in my component?
Only ngOnInit
Only button click event
Both ngOnInit and button click event
Why would we always subscribe a subscription in ngOnInit? The ngOnInit would be like a Page_Load in page life cycle, so it would only be called once at the very first time, if so whenever the subscription gets updated, will the ngOnInit be fired over and over again? If so, will my component be loaded over and over again which would cause a performance issue if in large application?
You usually put Observables to subscribe to inside a Service and make them available via getters and setters.
When subscribing to an Observable it behaves in a certain way like an EventListener. Whenever the object inside the Observable gets changed, an Event gets fired and your code inside the subscription gets executed. Additionally, you get provided the updated object.
Even if you init the subscription inside ngOnInit this won't cause your entire Component to reload when an update arrives. Only those parts that get updated by your code inside the subscription.
You don‘t have to put a subscription inside ngOnInit(). It depends on what you want to achieve in the component. But most of the time you want to load and display data directly when you access the component and update the UI when this data changes. That's why it is good practice to put the subscription in ngOnInit().

Can anyone explain more on VueJS Async Update Queue?

I am reading the vue official doc on Async Update Queue, and got confused on:
Whenever a data change is observed, it will open a queue and buffer
all the data changes that happen in the same event loop
what does it mean by the same event loop, are there gonna be multiple event loops at the same time? It also says the queue will be flushed in the next event loop “tick”, what does tick mean here? what if there is no next event loop or the next event loop never tick?
Let me try answering this question as par my understanding.
what does it mean by the same event loop, are there gonna be multiple event loops at the same time?
Instead of applying each and every change in any vue property/method/variable to the view, vue saves all those changes in queue and flush those changes later. In this process of pushing the changes in the queue, it optimises by not re-rendering on each change as is commented here:
/**
* Push a watcher into the watcher queue.
* Jobs with duplicate IDs will be skipped unless it's
* pushed when the queue is being flushed.
*/
export function queueWatcher (watcher: Watcher) {
...
...
the queue will be flushed in the next event loop “tick”, what does tick mean here?
Internally Vue tries native Promise.then and MutationObserver for the asynchronous queuing and falls back to setTimeout(fn, 0). Here you can see first preference for this is given to native Promise.then after that MutationObserver where native Promise is not available and in worst case fallback to setTimeout.

Cancelling the onChange event of a dijit/form/FilteringSelect

I need to be able cancel the onChange when a user tries to change value in FilteringSelect depending on if another action has made the information dirty or not.
I have tried to use the FilteringSelect's undo() method, but it's not working. I've tried aspect.before for the onChange event, but that seems to be hooked too late since the control has already got the new value. I tried to hook to the method that calls onChange, but have not been able to find that method.
How do I best accomplish this?
Once the value is set to this widget, the value of "_lastDisplayedValue" also updates. So, undo is not apt for this case.
If the onChange event handler on this widget need to be detached, based on some conditions, "_onChangeActive" private flag can be made use. This is not supposed to be used by programmers explicitly and is for internal purpose of the widget. But, for this case, this can give a hack.
Set _onChangeActive=false, and the handler will be detached. But do not forget to reset it to true again, else, onChange function does not work, if this is set to false.
e.g:
var widget = dijit.byId('widgetId');
widget.set('_onChangeActive',false);
widget.set('value',newValue); //onChange handler function does not run
//not just programmatic,
//even if user tries to select, when onChangeActive=false this works
widget.set('_onChangeActive',true);
//now the onChange is resumed back