AngularFire: How to $watch on child of an object? - angularfire

I want to listen changes done to a particular child of an object.
For ex.
I have data stored in firebase as
users: {
user1:{
info:{ name: 'Jon', age:'21'},
otherThings:{}
},
user2:{
info:{ name: 'Mark', age:'20'},
otherThings:{}
}
}
Now, I can listen to changes in user1 by
$scope.userObj = $firebase(userRef).$asObject();
$scope.userObj.$watch(function() {
console.log("data changed!");
});
But is there any way to listen to changes only when its child i.e. $scope.userObj.name changes?
It should be with userObj only, userObj.child('name') or something like that.

Angular's $watch command accepts dot notation. Just watch the appropriate field demo:
$scope.$watch('userObj.info.name', callback);
Read more in the $scope docs. This is pretty much Angular 101.
But most likely, this is the wrong approach. Any time you start trying to $watch and manipulate data in the controller, you've probably went down the wrong route. Let the view handle watching, and do data transformations either in a service or (in AngularFire's case) using data transformations like $extendFactory.

Related

vue-router : why watch on $route trigger when previous queries rewrite with same values

i have a button in my project when i click on it two queries added to URL
onClickBtn(){
this.$router.push({queries: {name:'kevin' , age:21} })
}
and I have a watch on $route
watch:{
$route:function(){
// call API
}
}
and when i click on that button severall time
watch calls my API every time although nothing has changed
and this make a problem form me
because nothing has changed in route But API is called and the same data is
received .
what should I do to avoid calling API in watch , when queries don't changed ??
The object you are pushing on the router is always different, that's why the $route watch is launched.
You can compare the data you receive in the watch, so that when they are different then you invoke the API:
watch:{
'$route' (newRoute, lastRoute){
// Check if query is different
// call API
}
}
On top of the answer that Cristian provided, you could also even double-check if your stuff has changed before even pushing a new object to your router.
Like this
checkIfUpdateNeeded && this.$router.push({queries: {name: 'kevin', age:21 } })
That way, you will have less moving parts and you won't have a trigger in the watcher for "nothing", especially if you're pushing a bigger object and want to make a deep-diff between 2 objects.

Prevent $emit from emitting more than once in Vue

I have an emit call within my Vue project to update search results, but the emit is being called at least 4 times, because the emit call is defined at various spots, so the data is sometimtes emitted with it and at other spots it is not.
I am using a global bus to perform an emit.
this.$root.bus.$emit('resetValuesInInfiniteLoader', filteredEntities);
this.$root.bus.$on('resetValuesInInfiniteLoader', function (filteredEntities) {});
I tried to name the emits calls differently, and also tried to use a different global vue bus but both options did not really work well.
Any idea how I can do this in an efficient manner so that the $emit is always only called once? How do I need to set it up to ensure that the emit is always only called once? I also tried using $once, which did not work, or tried to destroy the $emit. Can someone give me a small fiddle example or so maybe so I understand how to do this in the right way?
I have found this to be the case also and feel that there are some problems with using it in multiple locations. My understanding is that global event busses are not recommended in most applications as they can lead to a confusing tangle of events. The recommendation is that you use a state management solution like vuex.
But anyway, just a couple of points with your code above. I don't know how you created your bus but I have known to create it as such:
//main.js
const EventBus = new Vue()
Object.defineProperties(Vue.prototype, {
$bus: {
get: function () {
return EventBus
}
}
})
This creates it and makes it global. It can then be triggered in a component or components with:
<button #click="$bus.$emit('my-event')">click</button>
or
methods: {
triggerMyEvent () {
this.$bus.$emit('my-event', { ... pass some event data ... })
}
}
and listened to:
created () {
this.$bus.$on('my-event', ($event) => {
console.log('My event has been triggered', $event)
this.eventItem = 'Event has now been triggered'
//this.$bus.$off('my-event')
})
},
I have found that it works sometimes. I don't know why but it will work then it will trigger several events and I think it is because it isn't finalised or something. You may note I have commented out this.$bus.off which certainly stops it but it then doesn't work again. So I don't know what that's all about.
So there you go, a total non-answer, as in, Yes I've had that too, No I cant fix it.
I went with using vuex store, it seems a lot easier to communicate with any component within the application, has the advantage of global communication, yet does not have the caveat of sending multiple actions such as emit events

Using one vuex module store in multiple sibling components

I have one global state with some modules.
now i have vue components for various parts of my page.
i have everything setup so /foo uses the foo store (this works).
the created method loads data from an API and writes it to the store
now i have /foo/bar as another (sibling) component, but it needs to access the same store as /foo, but i can't get it to work.
if i enter /foo/bar/ in the URL, there is nothing in the store.
but if i switch to /foo, and then back to /foo/bar, the data is in the store and being output correctly
I've tried registering /foo/bar as a child, which seemed to have no effect (and actually it's not really a child, but just another page with the same data..)
I also tried
state: {
...mapState([
'foo'
)]
}
in /foo/bar, but that doesn't seem to be the right way either
what is the best practice to
load data from API on created on any of a specified set of pages
access said data on any of those pages (i.e. sharing the same store)
i've tried all day to find a solution, but it seems I didn't understand something.
thanks for your help :)
EDIT
actually, while i read my question again, i think my whole problem is the data not being loaded (because the created method is not called). how can i make sure this happens on any page using the store and just once? i can't just write an api call in every created method, can i?
Well, I think just to summarize your problem could be called like you're not being able to access the same state between two different componentes.
What I do normally is that I make an API call from one component inside the method beforeMount, that will guarantee that once my component is created, the data will be available to be used.
Furthermore, after calling the api, I update my state so after that I can call it from everywhere.
One thing that you have to take care with is which component is loaded first?
If A is B's parent, then you should load data inside A.
However, if A and B are siblings, then you should load data inside both of them because you can access first either Component A or B, then you don't know when the data is going to be available. In that case, I would load the data in both of the components.
Also, add cache to your server so you don't need to load the same data again.
For example:
State
{
data: {}
}
Component A
export default {
name: 'Batch',
beforeMount() {
this.getDataFromAPI();
},
methods: {
// getDataFromAPI will store its return inside data with a mutation
...mapActions(['getDataFromAPI']),
randomMethod() {
// Now I can Use my state
const data = this.$store.state.data;
}
}
};
Component B
export default {
name: 'Batch',
methods: {
randomMethodB() {
// If component A was loaded first than component B and A is B's parent, then the state will be accessible in the same manner and it should be populated
const data = this.$store.state.data;
}
}
};
Actions
const getDataFromAPI = ({ commit }) => new Promise((resolve, reject) => {
// Call server
const data = await callServer();
commit('updateMyStateWithData');
resolve(data);
});
export default {
getDataFromAPI
}
Mutations
const mutations = {
updateMyStateWithData(state, newData) {
state.data = newData;
}
}
export default mutations;
Another thing that I do is to define getters, that way is a good approach to load data once, and inside the getter you update the data to return only the things that your UI needs.
I hope that it helps!

Vue component .$on use case

Its not clear to me how to use the .$on(...) method available in every Vue instance. I am sure I am probably missing some use case where an event would be emitted and consumed by the same Vue component (?) but currently I am not able to imagine many. Also, where would this wiring be performed. Would that be in a lifecycle method ?
My problem: I have unrelated (that is non-sibling, non-descendant or non-common-parent) components which change view based on interactions made on a different component. And, $on(...) does not seem to help my purpose.
And, there arises the need to understand how/why .$on(..) is made available in the framework. Thank you.
You can use the $on-method for implementation of CommunicationHub -- common mixin, for non parent <--> child communication (like in your case).
For example: you have two Vue root applications: RootAppA and RootAppB. To communicate between them, you can create CommunicationHub mixin with next code:
let CommunicationHub = new Vue();
Vue.mixin({
data: function () {
return {
communicationHub: CommunicationHub
}
}
});
Now you can send data by emitting custom event from RootAppA with $emit-method, and get this data by subscribing on this event in RootAppB, with method $on:
let RootAppA = {
methods: {
sendData(){
this.communicationHub.$emit('customEvent', {foo: 'bar', baz: 1, comment: 'custom payload object'});
}
}
}
let RootAppB = {
created(){
this.communicationHub.$on('customEvent', (payload) => {
console.log(payload); //{foo: 'bar', baz: 1, comment: 'custom payload object'}
});
}
}
By the way, please mention that CommunicationHub-pattern is not so flexible solution for bigger apps. So if your application will grow up, perhaps you will want to use Vuex-library (see my example in previous so-answer)

How can I watch synchronously a state change in vuex?

I am using an opensource vuejs + vuex project and this is the source https://github.com/misterGF/CoPilot/tree/master/src/components
I am currently having problems knowing how to trigger an event from one components to another.
I can use this.$state.store.commit("foo", "bar") to store information in vuex, but when two seperate have two difference export default {} I don't know how I can make the app aware whenever "foo" is for exampled changed to "baz" ... unless I refresh/reload the app, there is no way for me to know the changes
Use this.$store.watch on your component. Created() is a good place to put it. You pass it the state to watch and then specify the callback for when it changes. The Vuex docs do not give good examples. Find more information on this Vuejs Forum page. Store watch functions the same as the vm.$watch, so you can read more about that here in the Vue docs.
this.$store.watch(
(state)=>{
return this.$store.state.VALUE_TO_WATCH // could also put a Getter here
},
(newValue, oldValue)=>{
//something changed do something
console.log(oldValue)
console.log(newValue)
},
//Optional Deep if you need it
{
deep:true
}
)
Your question is not entirely clear so I am going to make some assumptions here.
If you simply want your app to know when a store value has changed you can use a watcher to watch a computed property that is directly linked to a store getter:
So you would set a watcher on something like this:
computed: {
doneTodosCount () {
return this.$store.getters.doneTodosCount
}
},
watch:{
doneTodosCount(value) {
console.log(`My store value for 'doneTodosCount' changed to ${value}`);
}
}
If you want your commit to behave differently depending on what the current value of your foo property is set to, then you can simply check for this in your commit function.
Let me know if you have some more questions.