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();
}
});
Related
I've been trying to understand async, promises, etc. and I think I have a basic understanding of it, but I'm not getting the results I expect.
I have a HTML table, with the following:
<table data-bind="visible: viewPrincipal()">
viewPrincipal() is a function that should return true or false. This does work at the most basic level if viewPrincipal() just consists of return false or return true. But what I'm trying to do is call an async function to get the true or false value from there.
function viewPrincipal() {
console.log("Seeing if person is in principal group");
return IsCurrentUserMemberOfGroup("Principal Members", function (isCurrentUserInGroup) {
console.log(isCurrentUserInGroup);
return isCurrentUserInGroup;
});
}
The console.log works, and returns a true or false as I'd expect it to. But I want the parent viewPrincipal() function to return that true or false value, and all I get is "undefined".
I understand why this is happening - the IsCurrentUserMemberOfGroup() function is taking a bit of time to complete - but I don't know how to fix it. I know how to chain functions together, but when I'm trying to use something like knockout.js to determine if a table should be visible or not, I don't know how to chain.
Can anyone help?
The best way is to use an observable bool, and let your a-sync function change it's value. Let the magic of two-way-bindings do the rest.
Example:JSFIDDLE
function vm() {
this.viewPrincipal = ko.observable(false);
};
var vm = new vm();
ko.applyBindings(vm);
function fakeAsync() {
setTimeout(() => {
vm.viewPrincipal(true);
}, 1500);
}
fakeAsync();
I am a bit lost with your approach, but I'll try to help.
First, please double-think whether you really want to implement access control on the client side. Simply hiding an element if the user does not have sufficient rights is pretty dangerous, since the (possibly) sensitive content is still there in the DOM, it is still downloaded, all you do like this is not displaying it. Even a newbie hacker would find a way to display it though - if nothing else he can simply view it using the F12 tools.
Second, is that triple embedding of functions really necessary? You have an outermost function, that calls a function, which, in turn, calls the provided callback. You could clear this up by using computed observables:
function viewModel() {
var self = this;
var serverData = ko.observable(null);
this.viewPrincipal = ko.computed(function() {
var srvDataUnwrapped = serverData(); // access the inner value
if (!srvDataUnwrapped) {
return false;
}
// Do your decision logic here...
// return false by default
return false;
});
// Load the permission details from the server, this will set
// a variable that the viewPrincipal depends on, this will allow
// Knockout to use its dependency tracking magic and listen for changes.
(function() {
$.ajax(url, {
// other config
success: function (data) {
serverData(data);
}
);
})();
};
var vm = new viewModel();
and then in your view:
<table data-bind="visible: viewPrincipal">
note the lack if ()'s here, it is an observable, so Knockout will know how to use it.
If this seems overly complicated to add to your already existing code, then you could simply define an observable instead, and set the value of that inside your callback:
function viewModel() {
// other stuff ...
this.viewPrincipal = ko.observable(false);
// Call this wherever it fits your requirements, perhaps in an init function.
function checkPrincipal() {
IsCurrentUserMemberOfGroup("Principal Members", function (isCurrentUserInGroup) {
viewPrincipal(isCurrentUserInGroup);
});
};
};
With this approach, the markup would be the same as in the previous one, that is, without the parentheses:
<table data-bind="visible: viewPrincipal">
Doing it this way will simply set the inner value of an observable inside the callback you pass to IsCurrentUserMemberOfGroup, and because Knockout is able to track changes of observables, the value change will be reflected in the UI.
Hope that helps.
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; },
});
If one changes a scope property first, and then broadcasts an event second, will the corresponding watcher callback and event listeners callback always be executed in that same order? For example:
$scope.foo = 3;
$scope.$broadcast('bar');
and elsewhere:
$scope.$watch('foo', function fn1(){...});
$scope.$on('bar', function fn2(){...});
Will fn1 always be executed prior to fn2, or visa-versa, or can the order not be relied upon? Please cite sources, preferably to official angular docs.
In case it matters: lets assume the $scope.foo= and the $broadcast occur in a function invoked by an ng-click (i.e. user interaction)
[aside] Sorry question title is sloppy - please rename if you have something better.
To understand what is happening, you need to understand Angular's $digest cycle and event $emit and $broadcast functions.
Based on some research, I've also learned that Angular does not use any kind of polling mechanism to periodically check for model changes. This is not explained in the Angular docs, but can be tested (see this answer to a similar question).
Putting all of that together, I wrote a simple experiment and concluded that you can rely on your event handlers running first, then your watch functions. Which makes sense, because the watch functions can be called several times in succession during the digest loop.
The following code...
template.html
<div ng-app="myApp">
<div watch-foo ng-controller="FooController">
<button ng-click="changeFoo()">
Change
</button>
</div>
</div>
script.js
angular.module('myApp', [])
.directive('watchFoo', watchFooDirective)
.controller('FooController', FooController);
function watchFooDirective($rootScope) {
return function postLink(scope) {
scope.$watch(function () {
return scope.foo;
}, function (value) {
console.log('scope.$watch A');
});
scope.$on('foo', function (value) {
console.log('scope.$on A');
});
$rootScope.$on('foo', function (value) {
console.log('$rootScope.$on A');
});
$rootScope.$on('foo', function (value) {
console.log('$rootScope.$on B');
});
scope.$on('foo', function (value) {
console.log('scope.$on B');
});
scope.$watch(function () {
return scope.foo;
}, function (value) {
console.log('scope.$watch B');
});
};
}
function FooController($scope) {
$scope.foo = 'foo';
$scope.changeFoo = function() {
$scope.foo = 'bar';
$scope.$emit('foo');
};
}
...yields the following results in the console when the 'Change' button is clicked:
scope.$on A
scope.$on B
$rootScope.$on A
$rootScope.$on B
scope.$watch A
scope.$watch B
UPDATE
Here is another test that illustrates the watch callback being called twice in the digest loop, but the event handlers not being called a second time: https://jsfiddle.net/sscovil/ucb17tLa/
And a third test that emits an event inside the watch function, then updates the value being watched: https://jsfiddle.net/sscovil/sx01zv3v/
In all cases, you can rely on the event listeners being called before the watch functions.
I've got a recursive function which calls another asynchronous function, and upon resolving the promise, calls itself again after a few seconds:
$scope.gamePolling = function () {
if ($scope.getGames) {
$scope.getGameData().then(function () {
$timeout(function () {
$scope.gamePolling();
}, 3000);
});
}
};
When changing the route / state (using ui-router), I thought the $scope should be destroyed so I could turn off the recursive function using:
$scope.$on('destroy', function () {
$scope.getGames = false;
});
However, on the next page the gamePolling function keeps calling itself because the breakpoint inside the destroy never gets hit.
So my question is why isn't the $destroy event being triggered?
p.s. this also happens when removing the $timeout, so the problem must be with the recursion.
I've gotten around this problem, by turning off gamePolling() in the $stateChangeStart:
$scope.$on('$stateChangeStart', function () {
$scope.getGames = false;
});
So the polling stops but the $destroy event still doesn't seem to be triggered.
As a little test, in state/controller A I assigned the current $scope to a $rootScope variable so I could check if it was destroyed in the state/controller B: $rootScope.testScope = $scope;
When checking $rootScope.testScope.$$destroyed in controller B, it returned true. So it looks like the $scope of the controller A was successfully destroyed. However, in $rootScope.testScope I can still access the variables that were assigned to $scope.
It is "$destroy" event, not "destroy"
It is funny because you mention the event with the right name and in the code you are missing the $ sign prefix.
Hope it helps!
UPDATE: If you allow me I suggest you use an interval instead of a timeout+recursive function. You can then "kill" the interval in the $destroy event handler.
I have a function let's say:
$scope.addNode = function (param) {
//this is a function to add a child to a tree view node, sent via the param argument
var newNode = {
//add the different properties I need for the new node
};
if(param.hasOwnProperty('children') && param.children != null) {
param.children.push(newNode);
}
else {
param.children = [];
param.children.push(newNode);
}
$scope.$apply(); // calling $apply because I need the newNode to be rendered
$scope.setFocusedNode(newNode); //highlight the new node, change attributes, etc
$scope.editNodeText(newNode); //call inline Editing for the new node, which also involves DOM manipulation; this is basically where everything fails because without the apply, the DOM element for the newNode doesn't exist.
}
I use this same function from a jquery keyup event and from a ng-click directive.
The code works okay from the keyup event but when calling it from the directive I get an "$apply already in progress" error because ng-click already does the $apply innately.
However, removing the $apply also doesn't work because I need the scope to be updated for the code following it AND I can't replace ng-click with a normal onclick because the click function is also a property of an object in the scope and can change.
Is there a way to say "refresh scope here" without getting the "$apply already in progress" error? Note that Even tho I get the error, the scope gets updated corectly and works okay even when being called from ng-click (except in IE which just chokes and the javascript stops working altogether)
the right way is to remove $scope.$apply from the function and
when calling from jquery use
$scope.apply($scope.addNode(arguments));
and when calling the function from within angular use
$scope.addNode(arguments);
Can you try this?
$scope.addNode = function (node) {
$scope.$apply(function () {
//do something to $scope object
});
//do something that needs the scope to be refreshed
}
I found the solution to the problem. The code now looks like this:
$scope.addNode = function (param) {
//this is a function to add a child to a tree view node, sent via the param argument
var newNode = {
//add the different properties I need for the new node
};
if(param.hasOwnProperty('children') && param.children != null) {
param.children.push(newNode);
}
else {
param.children = [];
param.children.push(newNode);
}
$timeout(function () {
$scope.setFocusedNode(newNode); //highlight the new node, change attributes, etc
$scope.editNodeText(newNode); //call inline Editing for the new node, which also involves DOM manipulation; this is basically where everything fails because without the apply, the DOM element for the newNode doesn't exist.
}, 0);
}
And I simply called $scope.$apply(addNode) when calling it from the jquery keyup function.
What the $timeout does is delay the two functions until the browser has finished rendering the changes to the $scope. I don't fully understand how it does it but it works for now.