This is my current implmentation to fire callback on customVar get change using $watch...
module.directive('mudirective', function() {
return {
scope: {
callback: '&'
},
template: '<h1>Hello</h1><button ng-click="changeVaar()>Click</button>"',
controller: function($scope) {
$scope.customVar = false;
$scope.changeVaar = function() {
// some large logical execution
// which set customeVar
$scope.customVar = '';//some value assgined
};
},
link: function($scope) {
$scope.$watch('customVar', function() {
$scope.callback();
});
}
};
});
But i would like to replace this $watch with setter...
Can anybody has idea how could it be possible?
OR
Other option to avoid $watch function but fire callback on customVar changes.
But callback should be fire once it is confirmed that customVar
has changed in directive itself.
First, I will answer the comments under the question. I had this use case when I saw a controller putting a watcher on a scope value only to detect changes while the value was changed only by assignments inside the controller itself...
The watch was calling a function updating the UI depending on the assigned value (null or not, whatever).
Of course, we could call this function on each assignment. Or replace the watch with a function setting the value given as parameter, and calling this function. But somehow, using a setter was more "transparent", made a minimal set of changes, and you are sure not to miss an assignment.
On hindsight, it is similar to the way MobX works (go see this library if you have complex dependency watching to do).
Second, here is how to do it:
Object.defineProperty($scope, 'watchedValue',
{
set(newValue) { $scope._watchedValue = newValue; this.doSomethingWith(newValue); },
get() { return $scope._watchedValue; },
});
Related
In my controller I want to be notified when a variable value is changed. My requirement is when value of a variable will change a function will be invoked. So I am using $watch. My code is as follow.
var Canvas = angular.module('canvas');
Canvas.controller("excelOperation",function($scope,Data,SharedData,$http){
$scope.tbody=$scope.$parent.shared.previews;
$scope.$watch('$parent.shared.previews',function(newVal,oldVal){
console.log("WORKING");
})
setInterval(function(){
console.log($scope.$parent.shared.previews);
},1000)
/**
* Populate table function to populate excel table
*/
$scope.populateTable=function()
{
}
})
angular.bootstrap(document.getElementById("page"), ['canvas']);
But issue is $watch is working only when I refresh my page. Though the setInterval is printing the changed value of the variable $watch is not being invoked.
N.B. $scope.$parent.shared.previews is an object
What am I doing wrong?
And what I told to achieve, is this a good way to do?
You are watching a object's property change, deep watch is required. Second, to watch parent scope variable, maybe you'd better write like this $scope.$parent.$watch(...).
var deepWatch = true;
$scope.$watch('$parent.shared.previews', function(newVal, oldVal) {
console.log("WORKING");
}, deepWatch);
How to stop $watch while changing the object
Here is a $watch function
$scope.$watch($scope.OneTime,function(old,new)
{
// my function
});
The above $watch function will be fire whenever my (OneTime) object value has been changed.
But I won't to watch the object on every change, I just want to fire the $watch function when I change the my object on first time only.
I also tried something and find out a function from angular.js script file But I don't know what the below function doing exactly.
You can find this function from angular.js script file
function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression) {
var unwatch, lastValue;
return unwatch = scope.$watch(function oneTimeWatch(scope) {
return parsedExpression(scope);
}, function oneTimeListener(value, old, scope) {
lastValue = value;
if (isFunction(listener)) {
listener.apply(this, arguments);
}
if (isDefined(value)) {
scope.$$postDigest(function () {
if (isDefined(lastValue)) {
unwatch();
}
});
}
}, objectEquality);
}
But am seeing a pretty word unwatch();inside the function . So i think I need to use $unwatch for the object when end of the $watch function. But I couldn't get anything about $unwatch concept anywhere in angular document. but I can see it on angular script.
I had some idea about manually stop this $watch function by this below way
var unwatch = $scope.$watch("OneTime", function() {
//...
});
setTimeout(function() {
unwatch();
}, 1000);
But I am thinking about if angular provide to unwatch function to stop the abject watching, it would be easy to handle in my whole application. So planed to take override something in angular.js file in my application. let me know if you have any idea about override angular.js script file to create $unwatch function as same as $watch function. And also let me know angular had any$unwatch function.
I think you need one way binding over here
you can achieve this br
{{::oneTime}}
in your html page One-time expressions will stop recalculating once they are stable, which happens after the first digest
var $unwatch=$scope.$watch('onetime',function(){
unregister();
}
AngularJS does already provide such function, exactly as you mentioned above. When you create a watcher, it returns you a function that may be used to stop watching it.
From the $rootScope.Scope documentation,
$watch(watchExpression, listener, [objectEquality]);
Returns: function() Returns a deregistration function for this listener.
The only thing you need to do to unwatch your object would be calling the returned function. You could call it inside your watch function so it will be executed at the first time your watcher is invoked.
var unwatch = null;
// start watching the object
var unwatch = $scope.$watch($scope.OneTime, function(old, new)
{
// my function
if (unwatch != null) {
unwatch();
}
});
I'm building a directive that decorates a table header item and sort the data upon the click event. I cannot apply the changes on the data model to the parent scope using the directive scope.
vm.data is an array on the parent scope that contains the data I want to sort in the directive.
After the click the data object in the directive has changed but the parent is still in the same order.
I dont want to access the parent scope using $parent, What I'm missing ??
<th sortable="browser" data="vm.data">Browser</th>
directive code:
angular
.module("app")
.directive("sortable", ['lodash', sortableDirective]);
function sortableDirective(lodash) {
return {
restrict: "A",
scope:{
data:"="
},
controller:function($scope){
},
link: function (scope, element, attributes) {
var sorted = undefined;
var col = attributes['sortable'];
var oldClass = 'sorting'
attributes.$$element.addClass(oldClass);
$(element).on("click", sort);
function changeClass(){
if(sorted=='asc'){
attributes.$$element.removeClass(oldClass);
attributes.$$element.addClass('sorting_asc');
oldClass = 'sorting_asc';
}
else if(sorted=='desc'){
attributes.$$element.removeClass(oldClass);
attributes.$$element.addClass('sorting_desc');
oldClass='sorting_desc';
}
}
function sort() {
if (sorted == 'asc') {
sorted = 'desc';
}
else {
sorted = 'asc';
}
scope.data = lodash.sortBy(scope.data, function (o) {
return o[col];
});
if (sorted == 'desc') {
lodash.reverse(scope.data);
}
changeClass();
}
}
};
}
This is because you are using jQuery to listen to change on the element. So just change this line:
$(element).on("click", sort);
to
element.on("click", sort);
The 2nd attribute i.e. element is already an instance of jQlite if jQuery is not available and will be an instance of jQuery if jQuery is available.
In any case, there is a method available .on which will be executed on the value change. Since you again wrapped it to $(), the Angular was not getting notified of the change in the data.
Edit:
On the 2nd walk through of your code, I see the actual problem. You are reassigning the complete scope.data in the sort() method which is breaking the pass by reference behavior of Javascript (or in any OOPS programming).
The pass by reference will only work if you continue to modify your SAME reference variable. Noticed the word, SAME?? By writing scope.data = lodash.sortBy(scope.data, function (o) {}) you removed the reference of the actual data passed to the directive. Hence the values are not updated.
So to fix this problem, you have a few options:
Change your sorting code to not reassign the complete scope.data variable RECOMMENDED (use inbuilt sort method)
Pass the modified data to the parent scope using scope.$emit()
Or use the $parent property which you don't want to use
The bidirectional binding will update the parent on each digest cycle but the click handler needs to invoke that digest cycle with $apply:
link: function (scope, element, attributes) {
var sorted = undefined;
var col = attributes['sortable'];
var oldClass = 'sorting'
element.addClass(oldClass);
//REPLACE this
//$(element).on("click", sort);
//
//WITH this
element.on("click", function (e) {
sort();
scope.$apply();
});
function changeClass(){
if(sorted=='asc'){
element.removeClass(oldClass);
element.addClass('sorting_asc');
oldClass = 'sorting_asc';
}
else if(sorted=='desc'){
element.removeClass(oldClass);
element.addClass('sorting_desc');
oldClass='sorting_desc';
}
}
The ng-click directive automatically invokes $apply but when the click event is handled by AngularJS jqLite, the code needs to notify the AngularJS framework.
From the Docs:
$apply([exp]);
$apply() is used to execute an expression in angular from outside of the angular framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). Because we are calling into the angular framework we need to perform proper scope life cycle of exception handling, executing watches.
-- AngularJS $rootScope.scope API Reference -- $apply
Is there a way to add watch to a non scope variable. I want to add a watch to local variable. I have something like this
function EditAssetRegistryController(assetregistryService, manufacturerService, assettypeService, projectService, $localStorage, $routeParams) {
var vm = this;
vm.manufacturers = [];
vm.projects = [];
vm.asset_types = [];
vm.ch_group_uniq = 'none';
}
here is there a way to add watch to vm.ch_group_uniq?
I know how it will be done with scope variable but I have scenarios where I have to check many complex variables.
Well, you can easily add a watch for anything by passing a function as the first parameter:
$scope.$watch(function watchFunction(scope) {
return vm.ch_group_uniq
}, handler)
A few things to consider: watchFunction must return the same value if nothing has changed. This can lead to some gotchas, for example, returning the result of some array operations: [1,2,3].filter(...) will always return a new array, and lead to an endless $digest cycle. Also note the third parameter of $scope.$watch, which indicates whether to use an identity comparison, or angular.equals when comparing the values. (Check out the docs for further information - https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$watch)
However, your specific problem seems to be trying to use controllerAs and custom $watch-es. There's a very handy library that addresses this issue specifically: https://github.com/christopherthielen/angular-360-no-scope
$watch will not work as normal syntax with controllerAs. You need to bind it to $scope, and then you can watch that variable:
Code
$scope.$watch(angular.bind(this, function (ch_group_uniq) {
return this.ch_group_uniq;
}), function (newVal, oldVal) {
console.log('Name changed to ' + newVal);
});
Here is the reference Todd Motto Article
A cleaner syntax using ES6
$scope.$watch(() => {
return this.thingToWatch;
}, (newVal, oldVal) => {
// Your code here...
});
I am trying to watch a value in my controller. When it changes, I want to send out a broadcast, but I never get inside the $watch function. Here is the function:
$scope.$watch($scope.selectedEncounter, function(selectedEncounter) {
$scope.$broadcast('selecteRowChange', { encounter: selectedEncounter });
});
Can I watch something attached to the scope? If so what is the issue I am having with this code. If not, how do I implement this code to work?
You should pass either a function or a property name to your $watch function.
So, in your case, you should just change your code to:
$scope.$watch('selectedEncounter', function(value) {
// ...
});
Here is some more info from the docs.
The object you are watching is a complex object. Hence you should set objectEquality to true in your code as follows:
$scope.$watch('selectedEncounter', function(selectedEncounter) {
// ....
}, true);
Notice the true value as last parameter to the $scope.$watch function at the end.