I have multiple different watch statements in a directive that watch scope and re-render a d3 directive on different events (window resize, form submit, etc...)
window.onresize = function() {
scope.$apply();
};
// Call render function on window resize
scope.$watch(function() {
return angular.element($window)[0].innerWidth;
}, function() {
scope.render(scope.data);
});
// Watch for data changes and re-render
scope.$watch('data', function() {
return scope.render(scope.data);
}, true);
Problem is, I'm ending up calling render multiple times for the same event. Is there any way I can merge these three different $watches into a more concise structure? I had guessed that watching scope would also watch the data attribute of scope, but apparently not! Why is this?
Might not be the best solution, but it may help.
underscorejs' throttle takes a function and a minimum time between executions of the function and returns a function that will only call the function specified at most once every wait milliseconds.
_.throttle(func, wait, [options])
info
Related
On a function call I need to check for a change in a array element.
If any change occurs I need the function to continue or else to stop the execution.
$scope.myFunction = function() {
$scope.watchCollection('checkedCountry',function(newval,oldval) {
if (newval!==oldval) {
console.log("dothings"); // this is executing multiple times.
}
})
}
But for a single call of myFunction am getting output of multiple times dothings.
I need it to work only once for a function call and if the collection changes.
Based on the code you have shared, the number of watches that would be setup is directly proportional to the number of times the function myFunction is called. Therefore, everytime you call myFunction a new watch is setup. An change to the collection hence will trigger all the watches.
Normally watches are setup in some type of initialization function that is called only once.
I would suggest move your watch setup outside the myFunction call.
I have a controller. Here is the relevant part of the constructor function (what is the correct term for this function?):
activate();
function activate() {
$scope.$broadcast('ctrlLoadingStarted');
var promises = [getUsers()];
return $q.all(promises).then(function (eventArgs) {
$scope.$broadcast('ctrlLoadingFinished');
});
}
So the activate function is basically a generic function that takes in an array of data getting functions. It broadcasts on start and broadcasts on finish.
Then I have a directive nested in the controllers scope, here is the relevant part of the directive:
function link(scope, element, attrs) {
scope.$on('ctrlLoadingStarted', function (event, args) {
scope.spinnerOn = true;
});
scope.$on('ctrlLoadingFinished', function (event, args) {
scope.spinnerOn = false;
});
}
As you can see, it is simply listening to the start and finish events and turning a spinner on and off.
The issue is that the controller seems to be instantiating before the directive. This results in the broadcast going off and not turning the spinner on.
I tried to simply put scope.spinnerOn = true; in the first line of the directive (this way, no matter when the controller instantiates relative to the directive instantiation, the spinner will always go on). However, the problem I ran into there was that the controller's second broadcast would go out before the directive was instantiated as well causing an endless spinner.
I just want to ensure that if the controller instantiates before the directive but then the data-getting takes a while that the spinner will spin.
All advice is appreciated, thanks in advance.
Trigger the activate function in $timeout.
function activate() {
$scope.$broadcast('ctrlLoadingStarted');
var promises = [getUsers()];
return $q.all(promises).then(function (eventArgs) {
$scope.$broadcast('ctrlLoadingFinished');
});
}
$timeout(activate)
With this the activate function will be called in next digest cycle.
More about Digest cycle Integration with the browser event loop section at: https://docs.angularjs.org/guide/scope
Disclaimer: This is my understanding of how Javascript / browser works. It may not be correct.
Ran across a very "down-the-rabbit-hole" Angular question today I couldn't find the answer to. From the $scope docs, you can register an event handler on "$destroy", which is called right before a scope's destruction. That way, you can deregister event handlers like so:
var deregister = $scope.$on('myCustomEvent', function () {
// do some crazy stuff
});
$scope.$on('$destroy', function () {
deregister();
});
However, the $scope.$on('$destroy', ...) must create its own handler. Is that automatically destroyed, or do you have to do something like the following to destroy it?
var deregister = $scope.$on('myCustomEvent', function () {
// do some crazy stuff
});
var deregisterDestroy = $scope.$on('$destroy', function () {
deregister();
deregisterDestroy();
});
The answer is actually "maybe" depending on what you mean by it being automatically destroyed. If we look at the source for the $destroy method for scopes, we can see that while a $destroy event is broadcasted downward throughout child scopes, the actual $destroy method is never invoked on any scope but the initial one. That means that the actual cleanup and nulling out of properties never occurs on child scopes.
The reason that this doesn't leak memory is because once $destroy has been invoked on a scope, it becomes detached from the parent scope and is therefore eligible for garbage collection since it should no longer have any path to the GC Roots. This same logic applies to all child scopes since they also should have no paths to the GC Roots.
Your example is safe though; I do that myself in the same manner to clean up my own handlers when necessary and do not run into any kind of infinite loops.
I have a code that use $scope.$on one time on init and then in a function, so the code is executed multiple times. How can I unbind if first before I bind it again. I've try $scope.$off but there's not such function, https://docs.angularjs.org/api say nothing about $on. I'm using angular 1.0.6.
If you don't un-register the event, you will get a memory leak, as the function you pass to $on will not get cleaned up (as a reference to it still exists). More importantly, any variables that function references in its scope will also be leaked. This will cause your function to get called multiple times if your controller gets created/destroyed multiple times in an application.
Fortunately, AngularJS provides a couple of useful methods to avoid memory leaks and unwanted behavior:
The $on method returns a function which can be called to un-register the event listener.
Whenever a scope gets cleaned up in Angular (i.e. a controller gets destroyed) a $destroy event is fired on that scope. You can register to $scope's $destroy event and call your cleanUpFunc from that.
See the documentation
Sample Code:
angular.module("TestApp")
.controller("TestCtrl",function($scope,$rootScope){
var cleanUpFunc = $scope.$on('testListener', function() {
//write your listener here
});
//code for cleanup
$scope.$on('$destroy', function() {
cleanUpFunc();
};
})
$scope.$on returns a function which you can call to unregister.
This is probably a total newb question...apologies, but I can't get my head around it.
In a lot of angular documentation/examples I see asynchronous functions wrapped in 'timeout' blocks. Many are wrapped in setTimeout() and require the explicit use of
if (!$scope.$$phase) {
$scope.$apply();
}
Given that angular provides $timeout, the above code just seems outdated or wrong and within angular the use of $timeout should always be preferred. However, I digress.
Here is a snippet of some example code taken from: http://markdalgleish.com/2013/06/using-promises-in-angularjs-views/
var myModule = angular.module('myModule', []);
// From this point on, we'll attach everything to 'myModule'
myModule.factory('HelloWorld', function($timeout) {
var getMessages = function(callback) {
$timeout(function() {
callback(['Hello', 'world!']);
}, 2000);
};
return {
getMessages: getMessages
};
});
I see this wrapping of code in timeout blocks everywhere particularly related to asynchronous calls. But can someone explain why this is needed? Why not just change the code above to:
var myModule = angular.module('myModule', []);
// From this point on, we'll attach everything to 'myModule'
myModule.factory('HelloWorld', function() {
var getMessages = function(callback) {
callback(['Hello', 'world!']);
};
return {
getMessages: getMessages
};
});
Why wouldn't the code snippet above work just fine?
The use of $timeout or $interval is to implicitly trigger a digest cycle. The process is as follows:
Execute each task in the callback function
Call $apply after each task is executed
$apply triggers a digest cycle
An alternative is to inject $rootScope and call $rootScope.$digest() if you are using services that don't trigger a $digest cycle.
Angular uses a dirty-checking digest mechanism to monitor and update values of the scope during
the processing of your application. The digest works by checking all the values that are being
watched against their previous value and running any watch handlers that have been defined for those
values that have changed.
This digest mechanism is triggered by calling $digest on a scope object. Normally you do not need
to trigger a digest manually, because every external action that can trigger changes in your
application, such as mouse events, timeouts or server responses, wrap the Angular application code
in a block of code that will run $digest when the code completes.
References
AngularJS source: intervalSpec.js
AngularJS source: timeoutSpec.js
$q deferred.resolve() works only after $timeout.flush()
AngularJS Documentation for inprog | Digest Phases
The $timeout in your example is probably used just to simulate an async function, like $http.get. As to why $timeout and not setTimeout: $timeout automatically tells angular to update the model, without the need to call $scope.$apply()
Also, consider the following example:
$scope.func = function(){
$scope.showSomeLoadingThing = true;
//Do some long-running stuff
$scope.showSomeLoadingThing = false;
}
No loading thingy will be shown, you would have to write it like this:
$scope.func = function(){
$scope.showSomeLoadingThing = true;
$timeout(function(){
//Do some long-running stuff
$scope.showSomeLoadingThing = false;
});
}