Does using .on to bind click benefit from ngTouch? - javascript

Currently our project does not have ngTouch but it will at a future time. We are still learning Angular as we go.
I have this simple directive
app.directive('myDir', ['$log', function($log) {
return {
restrict: "A",
link: function($scope, iElm, iAttrs, controller) {
iElm.on('click', function($event) {
// functionality...
});
}
};
}]);
I am doing the binding of the click here instead of using ng-click because I don't want the html markup to show.
Will this type of binding take advantage of ng-touch when we include it? By that I mean if you use ng-click with ng-touch included then there will be no more 300ms delay after click/tap. So if I don't use ng-click and just use .on will it still work the same?

No, touchstart is a different event than click. You would need to do:
element.on('click touchstart', function (event) {
/* ... */
});
The above would register a handler for both events at the same time. Also keep in mind that because this event handler is triggered outside of an Angular digest cycle, you need to wrap its contents in $scope.$apply.
app.directive('myDir', function ($log) {
return {
restrict: 'A',
link: function ($scope, element, attrs) {
element.on('click touchstart', function (event) {
$scope.$apply(function () {
// functionality...
});
});
}
};
});
Edit: Re: Angular digest cycle.
When you do things inside of an angular controller or inside of an angular directive, all of that logic is running inside of an angular digest cycle. The digest cycle is basically angular's event loop. As long as angular is the one that launched a logic path then angular is able to ensure that things happen in the right order. When things happen in the digest cycle as expected angular is able to update the UI appropriately and all is well.
This is important to understand when you do things like register an event handler from inside an angular directive or controller. If the event is triggered by something that isn't wrapped in angular magic (like most of the native directives that register DOM event handlers for you behind the scenes) then that logic path is happening outside of an angular digest cycle. The side effect you'll likely see is that even though the code runs and the scope data is modified as expected, the UI will not be updated. The next time that scope variable changes in a digest cycle suddenly you'll see the change that was made.
To fix this you need to manually register the logic you want to perform with angular so it can perform that logic at the appropriate time during a digest cycle, updating the UI as expected.To do this you simply wrap the logic you want to perform inside of $scope.$apply which just takes a simple callback. It then manually begins a digest cycle and calls the callback you supplied during that cycle.
If you're curious, this is literally all the ng-click directive does:
app.directive('ngClick', function ($parse) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var fn = $parse(attrs.ngClick, null, true);
element.on('click', function (event) {
scope.$apply(function () {
fn(scope, { $event: event });
});
});
}
};
});
It uses the built-in $parse utility to parse out the expression you supply to ng-click and execute it. It passes in the local scope to the expression so you can use variables in scope inside your expression. This is why you can do ng-click="myFunction(someScopeVar)". It also passes in a custom variable called $event available only inside that expression. Notice that it's just literally the JQuery wrapped DOM event. Notice also that Angular wraps the DOM event logic in a call to $scope.$apply so that it happens in a digest cycle appropriately.

Related

AngularJS $apply inside scope

Is there another approach which could be used to carry out the following? This jQuery function is used inside the Angular $scope, and it works fine however it throws an $apply error, essentially because it's seen as an $apply inside an $apply. Yet if I remove $scope.$apply() line it stops working.
function myfunction(start, end) {
// Lots of jQuery code here - omitted from this example
// Update the scope
$scope.myf = "abc";
$scope.myt = "def";
$scope.$apply();
}
myfunction();
Either wrap your code in a $timeout or use $apply(). Note that $timeout is calling $apply() internally. $apply is specially made to resync external changes (not in angular loop) with angular.
If your function trigger on click or on event. You can bind your function to angular by using the directive ng-click or ng-[event].
use $scope.$evalAsync() instead. it would not throw an error and do the same in this case.

Angular spinning directive gets instantiated after controller load

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.

How to unbind $on in Angular?

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.

why the need to use 'timeout' in angular

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;
});
}

AngularJS : execute code after his job

I want to execute some code after AngularJS finished to change the HTML after an event. I've tried to do the following:
angular.module('ngC', [], function($routeProvider, $locationProvider)
{
$locationProvider.html5Mode(true);
})
.directive("carousel", function () {
return function (scope, element, attrs) {
scope.$watch("imgs", function (value)
{
// Here my code
});
};
});
The problem is that my code is execute before AngularJS replace {{}} code but I want execute it after.
Just compare if newValue is different from oldValue in the $watch listening function, this will indicate if something has changed or not.
scope.$watch("imgs", function (newValue, oldValue)
{
if(newValue !== oldValue) {
// Here my code
}
});
EDIT: From the docs
After a watcher is registered with the scope, the listener fn is called asynchronously (via $evalAsync) to initialize the watcher. In rare cases, this is undesirable because the listener is called when the result of watchExpression didn't change. To detect this scenario within the listener fn, you can compare the newVal and oldVal. If these two values are identical (===) then the listener was called due to initialization.
I found my answer on: jQuery doesn't work in AngularJS ng-view properly
// (inside some function with $rootScope available)
$rootScope.$on('$viewContentLoaded', function() {
// ... (multiview init code here) ...
});

Categories