$watch works but not $watchCollection - angularjs

This works:
$scope.$watch('cities', function(){
alert("fire");
}, true);
This dosen't work:
$scope.$watchCollection('[cities]', function(){
alert("fire")
}, true);
Any ideas what can be wrong?
$scope.cities is hooked up to some checkboxes.

The documentation of $watchCollection states that it shallow watches the object passed to it and fires whenever the object changes. So in case of arrays, it fires when any of the elements changes, but not when some deep property inside the element changes. Also, it doesn't take a third argument (as $watch does for objectEquality). In your case, the object cities probably never changes in reference.
However, if you had:
$scope.cityList = [cities];
$scope.$watchCollection('cityList', function () { alert('fire'); });
// As a response to some event:
$scope.cityList[0] = newCities;
then the watcher should fire.

Related

$scope outsite $on listener return undefined

I have this small piece of code that it is not working:
$scope.$on('play', function(e, data) {
$scope.tick = (data.time*100)/2.5;
});
console.log($scope.tick);
If I log $scope.tick outside $on return undefined. I can't understand why, I need to access that var outside the event listener.
This is the code triggering the $on
angular.module('videoCtrl', ['vjs.video'])
.controller('videoController', ['$scope', 'Timeline', function ($scope, Timeline) {
$scope.mediaToggle = {
sources: [
{
src: 'http://static.videogular.com/assets/videos/videogular.mp4',
type: 'video/mp4'
}
],
};
//listen for when the vjs-media object changes
$scope.$on('vjsVideoReady', function (e, videoData) {
videoData.player.on('timeupdate', function () {
var time = {
time: this.currentTime()
};
$scope.$broadcast('play', time);
})
});
}]);
and here the one receiving it:
angular.module('mediaTimelineCtrl', ['mt.media-timeline'])
.controller('DemoMediaTimelineController', function ($scope, Timeline) {
$scope.tick = 100;
$scope.disable = false;
$scope.timelines = Timeline.getTimelines();
$scope.$on('play', function(e, data) {
$scope.tick = (data.time*100)/2.5;
});
console.log($scope.tick);
});
Thanks to everyone
$on is a method provided by angularjs that allows you to listen for a specific broadcast at which point the provided function will be executed.
You log the variable right after you registered your callback that sets the tick variable. At this point your callback simply hasn't been called yet. For this to happen you must use $scope.$broadcast('play', data) somewhere in your code(Depending on wher you call $broadcast you might have to use $rootScope instead of $scope, because broadcast only sends the events to child scopes.(See angular docs here for more info)
Edit: This has been resolved in chat now. The callback provided in $on was called correctly, but the video library that is being used here called the event outside of an angular $digest cycle. Wrapping the assignment of $scope.tick into $scope.$apply([...]) did the trick.
$scope.$on use to listen to the events that fired by the $broadcast or an $emit. until one of those event fires, on function is not gonna fire and the content of the on function will not gonna execute.
But since the console log is outside of the on function it will execute whether scope.on fires or not. that is why conosole.log shows undefined.
if you put the console inside the scope.on function then it will execute only when the event fires
The value of $scope.tick is updated only when the event is fired. At the end of first digest cycle the value of $scope.tick is still undefined. You have to initialize the value of $scope.tick first or use $watch [$watch(watchExpression, listener, [objectEquality]);]. watchExp The expression being watched. It can be a function or a string, it is evaluated at every digest cycle. listener A callback, fired when the watch is first set, and then each time that during the digest cycle that a change for watchExp‘s value is detected. The initial call on setup is meant to store an initial value for the expression.

scope.$watch within a controller not firing

I am using controllerAs syntax like so:
.state('operators', {
url: '/operators',
templateUrl: 'app/features/operators/operators.html',
controller: 'OperatorsController',
controllerAs: 'vm'
})
And have a variable in my controller vm.selectedOperators which I want to watch for changes on:
var vm = this;
vm.selectedOperators = [];
$scope.$watch(function() {
return vm.selectedOperators;
}, function(current) {
console.log(current.length);
});
In the view, an expression is used to show the count of items in the variable.
{{vm.selectedOperators.length}} item selected
This variable gets updated from elsewhere (Its being used by a multi-select table directive I have written which is used on the same view) and I see this expression updating fine in the view.
The problem is my watcher in the controller doesn't fire after the variable gets initialised, and I am not sure why.
Can anyone offer any help?
Thanks
UPDATE
I should have mentioned, I have already tried using the expression equivalent like so:
$scope.$watch('vm.selectedOperators', function (current){
console.log(current);
});
But this still does not fire for me
Concept: There is an optional third argument to the $watch function - objectEquality
$watch(watchExpression, listener, [objectEquality]);
You must set it to true when you are trying to watch an object otherwise angular will only match the reference of both the objects. By setting it true angular compares the old object and new using angular.equals
Read more about it here:
https://docs.angularjs.org/api/ng/type/$rootScope.Scope
$scope.$watch(function() {
return vm.selectedOperators;
}, function(current) {
console.log(current.length);
}, true);
Update
As #Martijn Welker pointed out in the comment below, $watchCollection can be useful if only single level equality has to be matched instead of complete object tree.
$scope.$watchCollection(function() {
return vm.selectedOperators;
}, function(current) {
console.log(current.length);
});
Try this (ES6 syntax):
$scope.$watch('vm.selectedOperators', (new, old) => {
console.log('new:', new, 'old:', old);
});
You should be able to see the changes.
Try using Deep Watch Array object..
Deep watch will watch each object in array list. It's little bit expensive in process than normal watch So use if normal watch doesn't work:-
Here is code
$scope.$watch('vm.selectedOperators', function (current) {
console.log(current.length);
},true);

$onInit does not immediately have bindings data

I have this component in my app:
<actions-bar actions="$ctrl.actionsBarData"></actions-bar>
Here is the controller / component definition:
.component('actionsBar', {
controller: 'actionsBarController',
bindings: {
actions: '<'
}
})
.controller('actionsBarController', function() {
var vm = this;
vm.$onInit = function() {
console.log(vm.actions);
setTimeout(function() {
console.log(vm.actions);
}, 500);
};
});
Take note of the two console.log statements within $onInit. In the browser the first logging statement prints undefined. The second statement, wrapped in setTimeout correctly prints out the this.actions object.
According to the Angular docs:
$onInit() - Called on each controller after all the controllers on an
element have been constructed and had their bindings initialized
If the bindings have been initialized, why is the data not immediately available? Why must I set a delay of 500ms before it is available?
If $onInit isn't the solution here, how can I reliably access the bindings data within my controller? It appears that this is correct lifecycle hook to use, and none of the other hooks appear to be what I want in this case.
This is because while the bindings have been initialized in the controller, they haven't necessarily been initialized where they are coming from. If you are populating your binding with an async call, the object is going to be undefined or empty.
$onChanges allows you to inspect changes that happen to your bindings.
vm.$onChanges = function (changesObj) {
console.log(changesObj.actions);
}
If what I described above is happening, this will fix it for you.
EDIT: a word

AngularJS 1.5.x $onChanges Not Working with One-Way Binding Changes

I don't understand why $onChanges isn't kicked off when I change a bound primitive in an input. Can someone see what I've done wrong, and explain this in an uncomplicated way? I made a plunkr of a quick test application after I couldn't get it to work in my actual application either.
angular
.module('test', [])
.component('test', {
template: '<child application="vm.application"></child>',
controller: 'testCtrl as vm'
})
.controller('testCtrl', function() {
var vm = this;
vm.$onInit = function () {
vm.application = {
data: {
name: 'Test'
}
}
};
})
.component('child', {
template: '<input type="text" ng-model="vm.application.data.name">',
bindings: {
application: '<'
},
controller: 'childCtrl as vm'
})
.controller('childCtrl', function() {
var vm = this;
vm.$onChanges = function (changes) {
console.log('CHANGED: ', changes);
};
})
The $onChanges method is not called for changes on subproperties of an object. Default changes to objects generally follow this sequence within a components lifetime:
UNINITIALIZED_VALUE to undefined
undefined to {} or { someAttribute: someValue, .. }
({..} to undefined if you delete the object in a parent scope)
In order to watch subproperties you could use the $doCheck method that was added in 1.5.8. It is called on every digest cycle and it takes no parameters. With great power comes great responsibility. In that method you would put logic that detects whether a certain value has been updated or not - the new value will already be updated in the controller's scope, you just need to find a way to determine if the value changed compared to the previously known value.
You could set a previousValueOfObjectAttribute variable on the controller before you start to expect changes to this specific attribute (e.g. when subcomponent B calls an output binding function in component A, based on which the target object - which is an input binding to B - in A changes). In cases where it is not predictable when the change is about to occur, you could make a copy of the specific atributes of interest after any change observed via the $doCheck method.
In my specific use case, I did not explicitly check between an old and new value, but I used a promise (store $q.defer().promise) with the intention that any change I would 'successfully' observe in the $doCheck method would resolve that promise. My controller then looked something like the following:
dn.$doCheck = function () {
if (dn.waitForInputParam &&
dn.waitForInputParam.promise.$$state.status === 0 &&
dn.targetObject.targetAttribute !== false)
dn.waitForInputParam.resolve(dn.targetObject.targetAttribute);
}
dn.listenToInputChange = function () {
dn.waitForInputParam = $q.defer();
dn.waitForInputParam.promise.then(dn.onInputParamChanged);
}
dn.onInputParamChanged = function (value) {
// do stuff
//
// start listening again for input changes -- should be async to prevent infinite $digest loop
setTimeout(dn.listenToInputChange, 1);
}
(w.r.t. promise.$$state.status, see this post).
For all other intents and purposes, watching changes to primitive data types, you should still use $onChanges. Reference: https://docs.angularjs.org/guide/component
It's $onChanges and not $onChange.
Also, the onChange only updates when the parent value is changed, not the child. Take a look at this plunkr. Note the console.log only fires when you type in the first input.
As others said above, Angular does not watch for changes in object properties, however, you can make Angular believe that your object is changed by reference.
It is sufficient to do a shallow copy of the object in order to trigger an $onChanges event:
vm.campaign = angular.extend({}, vm.campaign);
Credits to #gkalpak
Dealing with $onChanges is tricky. Actually, thats why in version 1.5.8 they introduced the $doCheck, similar to Angular 2 ngDoCheck.
This way, you can manually listen to changes inside the object being listened, which does not occur with the $onChanges hook (called only when the reference of the object is changed). Its the same thing, but it gets called for every digest cycle allowing you to check for changes manually (but better then watches).
For more details, see this blog post.

How can I bind this.save to a backbone.js model change event?

I want my model to save to the server every time it changes.
I have tried:
initialize: function() {
this.bind('change', this.save());
},
I'm new to Backbone so I'm willing to believe there is a better way to achieve this. Basically I want avoid calling model.save at other points in my code by just automatically saving to the server every time the model changes.
Careful with using this.save directly:
this.bind('change', this.save);
will cause the changed attributes sent with the change event to be passed in to the save method, which will cause a second 'change' event to be fired, and so it will be saved twice.
Instead use:
this.bind('change', function(){ this.save(); });
you are calling this.save immediately instead of passing it through as a callback function.remove the parenthesis on this.save:
initialize: function() {
this.bind('change', this.save);
},
and your model's this.save method will be passed in as a function reference, allowing it to be called when the model changes.

Resources