My intention is to watch a value within $rootScope, and call the listener method when value changed. However, I found that if value doesn't changed, the listener will also be called. Is there a way to watch if a value is changed in angular?
app = angular.module('myApp',[]);
app.controller('MyCtrl', function($rootScope, $timeout){
$rootScope.markers = 1;
$rootScope.$watch('markers', function(newValue, oldValue){
console.log('being watched oldValue:', oldValue, 'newValue:', newValue);
});
$timeout( function() {
$rootScope.markers = 1;
}, 500);
});
here is the code, http://plnkr.co/edit/MXIaJKE9UGTMjrreFjb0?p=preview
$watch runs when controller created, and then when value assigned, so you need to check equality inside $watch function:
if(oldValue != newValue) {...}
Related
I have using custom directive
<users stats="stats"></users>
When we change the scope object from main controller, i have updating directive scope value also
app.directive('users', function(){
var directive = {};
directive.restrict = "E";
directive.templateUrl = "templates/partials/users_stats.html";
directive.scope = {stats:'='};
directive.controller = "userStatsCtrl"
return directive;
});
So, Inside the directive controller, i am doing some func. like
app.controller("userStatsCtrl", function($scope){
$scope.$watch('stats', function(newValue) {
if (angular.isDefined(newValue)) {
.....
}
});
})
So here, i am using $watch to listen the scope, If it is update, I will do the some perform.
my question is, I dont want to use watch to listen the scope, If scope gets updated, i need to do some perform.
So how to update scope value without using scope.$watch
When ever "scope.stats" value get changed in your main controller, you can broadcast an event and receive the same event in your directive and do operation what ever you want.
Example code for broadcast an event:
$scope.$broadcast('yourEventName');
Receive an event in directive:
$scope.$on('yourEventName', function(){
});
how about trying:
<users stats="stats" ng-change="onUserStatChange($event)"></users>
or
<users stats="stats" ng-blur="onUserStatChange($event)"></users>
and then in controller:
$scope.onUserStatChange = function(event) {
//event.whatever
}
I might be using this wrong, but I have a function that watches a variable and when that variable is changed from the view, the function runs.. but when a sibling function changes that variable the watch doesn't run. Am I coding something wrong?
scope.$watch (settings.number, function(val){
alert('test');
})
scope.exampleObj = {
exampleFunc : function(){
settings.number += 5;
}
};
so when I call scope.exampleObj.exampleFunc(); shouldn't scope watch get called?
Replace string to a function or use $watchCollection, like this:
Using var:
angular.module('TestApp', [])
.controller('MainCtrl', function($scope){
// Object
var settings = {
number: 1,
foobar: 'Hello World'
};
$scope.$watch(function(){ return settings.number; }, function(newValue, oldValue){
console.log('New value detected in settins.number');
});
$scope.$watchCollection(function(){ return settings; }, function(newValue, oldValue){
console.log('New value detected in settings');
});
});
Using $scope:
angular.module('TestApp', [])
.controller('MainCtrl', function($scope){
// Object
$scope.settings = {
number: 1,
foobar: 'Hello World'
};
$scope.$watch('settings.number', function(newValue, oldValue){
console.log('New value detected in $scope.settings.number');
});
});
Example: http://jsfiddle.net/Chofoteddy/2SNFG/
*Note: $watchCollection that is available in AngularJS version 1.1.x and above. It can be really useful if you need to watch multiple values in a array or multiple properties in a object.
The watcher expects the name(ie. string) of a property of inside the scope, not the object itself.
scope.$watch ('settings.number', function(newval,oldval){
alert('test');
});
scope.exampleObj = {
exampleFunc : function(){
scope.settings.number += 5;
}
};
I'm assuming the somewhere you declare scope.settings.number first, and that you meant to set scope.settings.number not settings.number as you can only watch variables which are properties of the angular scope.
Though you may be doing the right thing with just settings.number += 5;, I may just be tired.
You need to set the objectEquality to true . so you can compare for object equality using angular.equals instead of comparing for reference equality.
for more infos visit the documentation
scope.$watch (settings.number, function(val){
alert('test');
},true)
scope.exampleObj = {
exampleFunc : function(){
settings.number += 5;
}
};
I have a directive in which I pass in an attrs and then it is watched in the directive. Once the attrs is changed, then an animation takes place. My attrs always is undefined when the $watch gets triggered.
App.directive('resize', function($animate) {
return function(scope, element, attrs) {
scope.$watch(attrs.resize, function(newVal) {
if(newVal) {
$animate.addClass(element, 'span8');
}
});
};
});
And here is my test:
describe('resize', function() {
var element, scope;
beforeEach(inject(function($compile, $rootScope) {
var directive = angular.element('<div class="span12" resize="isHidden"></div>');
element = $compile(directive)($rootScope);
$rootScope.$digest();
scope = $rootScope;
}));
it('should change to a span8 after resize', function() {
expect($(element).hasClass('span12')).toBeTruthy();
expect($(element).hasClass('span8')).toBeFalsy();
element.attr('resize', 'true');
element.scope().$apply();
expect($(element).hasClass('span8')).toBeTruthy();
});
});
When the attrs changes, my watchers newValue is undefined and so nothing happens. What do I need to do to make this work? Here is a plunker
You are not watching the value of attrs.resize; you are watching the value pointed by attrs.resize instead, in the test case a scope member called isHidden. This does not exist, thus the undefined.
For what you aare trying to do, the following would work:
App.directive('resize', function($animate) {
return function(scope, element, attrs) {
scope.$watch(
// NOTE THE DIFFERENCE HERE
function() {
return element.attr("resize");
// EDIT: Changed in favor of line above...
// return attrs.resize;
},
function(newVal) {
if(newVal) {
$animate.addClass(element, 'span8');
}
}
);
};
});
EDIT: It seems that the attrs object does NOT get updated from DOM updates for non-interpolated values. So you will have to watch element.attr("resize"). I fear this is not effective though... See forked plunk: http://plnkr.co/edit/iBNpha33e2Xw8CHgWmVx?p=preview
Here is how I was able to make this test work. I am passing in a variable as an attr to the directive. The variable name is isHidden. Here is my test with the updated code that is working.
describe('resize', function() {
var element, scope;
beforeEach(inject(function($compile, $rootScope) {
var directive = angular.element('<div class="span12" resize="isHidden"></div>');
element = $compile(directive)($rootScope);
$rootScope.$digest();
scope = $rootScope;
}));
it('should change to a span8 after resize', function() {
expect($(element).hasClass('span12')).toBeTruthy();
expect($(element).hasClass('span8')).toBeFalsy();
element.scope().isHidden = true;
scope.$apply();
expect($(element).hasClass('span8')).toBeTruthy();
});
});
I am able to access the variable isHidden through the scope that is attached to the element. After I change the variable, the I have to run $digest to update and then all is golden.
I feel that I should probably be using $observe here as was noted by package. I will look at that and add a comment when I get it working.
As Nikos has pointed out the problem is that you're not watching the value of attrs.resize so what you can try doing is this:
Create a variable to hold your data and create these $watch functions:
var dataGetter;
scope.$watch(function () {
return attrs.resize;
}, function (newVal) {
dataGetter = $parse(newVal);
});
scope.$watch(function () {
return dataGetter && dataGetter(scope);
}, function (newVal) {
// Do stuff here
});
What should happen here is that Angular's $parse function should evaluate attrs.resize and return a function like this. Then you pass it the scope and do something. As long as attrs.resize is just a boolean then newVal in the 2nd watch expression should be a boolean, I hope.
I am handling a JQRangeSlider through an angular directive + controller.
Within my controller, I am assigning new values to scope variables but there change is not notified to the controller scope (I am using isolated scope '=' in my directive) even though it is correctly updated.
// JQRangeSlider event handler
var valuesChanged = function(e, data) {
if (data.values.min && data.values.max) {
console.log('values changed', data.values); // this is always printed
$scope.minselectedtime = data.values.min; // not triggering $scope.$watch
$scope.maxselectedtime = data.values.max;
$scope.$broadcast('minchanged', $scope.minselectedtime); // instantly triggering $scope.$on('minchanged',...)
console.log('scope', $scope);
}
};
$scope.$watch('minselectedtime', function(newValue, oldValue) {
if (newValue === oldValue) {
return;
}
console.log('minselectedtime -->', newValue);
}, true);
$scope.$on('minchanged', function(event, min) {
console.log('minchanged ==>', min);
});
$scope.$watch('minselectedtime', ...) is only triggered on the next $scope modification (where $scope.minselectedtime is not even modified):
$scope.toggleTimeScale = function(refinementString) {
console.log('scale set to', refinementString);
$scope.timescale = refinementString; // year, month, day...
}
Why isn't $scope.$watch immediatly notified of the variable change?
The isolated code has its $scope.minselectedtime value changed but the parent scope doesn't see its value changed until $scope.toggleTimeScale is triggered.
Your method of using $scope.$apply() is correct. The reason being that the update from JQRangeSlider happened 'outside' of angularjs knowledge. If you read the documentation on the $apply method (http://docs.angularjs.org/api/ng.$rootScope.Scope#$apply) you'll see that it's used in the case you need.
We have a directive that has an optional attribute on it. If the attribute is not there, it provides a default value. The attribute is usually set from data in the scope (i.e., the value of the attribute is usually an expression and not a literal string. See http://jsfiddle.net/8PGZ4/
As such, we are using attrs.$observe in the directive to set up the scope properly. This works great in the app itself. However, when trying to test this (using Jasmine), the function in the $observe never gets run. Our test looks something like this:
describe("myDirective", function(){
function getDirectiveScope(compile, rootScope, directiveHTML)
{
return (compile(directiveHTML)(rootScope)).scope();
}
describe("foo", function () {
it("should return the default value", inject(function ($compile, $rootScope) {
var directiveScope = getDirectiveScope($compile, $rootScope, '<div my-directive></div>');
expect(directiveScope.bar).toBe("No Value");
}));
it("should now return the value given", inject(function ($compile, $rootScope) {
$rootScope.foo = "asdf";
var directiveScope = getDirectiveScope($compile, $rootScope, '<div my-directive foo="{{foo}}"></div>');
expect(directiveScope.bar).toBe("asdf");
}));
});
});
These then fail with the following errors:
Expected undefined to be 'No Value'.
Expected undefined to be 'asdf'.
I put a console.log in the $observe function to try to see what was going on, and when the tests run, I never see the log. Is there an explicit call I have to make for the $observe to run? Is there something else going on here?
You need to trigger a digest cycle to let the AngularJS magic happen in your test. Try:
it("should return the default value", inject(function ($compile, $rootScope) {
var directiveScope = getDirectiveScope($compile, $rootScope, '<div my-directive></div>');
$rootScope.$digest();
expect(directiveScope.bar).toBe("No Value");
}));