AngularJS $apply inside scope - javascript

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.

Related

Use $timeout to force execution, is a bad practice?

I am working with ionic 3. So I have this code on controller and it works
$scope.note = 'Lorem...';
$rootScope.$on('Active',function() {
$timeout(function() {
$scope.note = 'test';
},0);
});
But why this not work?
$scope.note = 'Lorem...';
$rootScope.$on('Active',function() {
$scope.note = 'test';
});
What is the best approach for this?
In this context it can be considered a bad practice. Spontaneous use of $timeout usually indicates that a developer doesn't know if the code runs inside or outside of digest cycle and tries to play it safe.
The explantion why this doesn't work stays outside of the scope of posted code, but the reason is that this code runs outside of a digest. This depends on where Active scope event is triggered, and this is what a developer should care about in the first place, since scope events don't necessarily happen inside of digest cycle.
If the event is known to happen outside of a digest, digest-dependent code should be wrapped with $apply:
$scope.$on('Active',function() {
$scope.$apply(function() {
$scope.note = 'test';
});
});
If the event is known to happen both inside and outside of a digest, code should be wrapped with $evalAsync:
$scope.$on('Active',function() {
$scope.$evalAsync(function() {
$scope.note = 'test';
});
});
$timeout(...) is supposed to be used only when it's behaviour is wanted, i.e. one tick delay or more and a digest.
As it was suggested by #georgeawg, it is also a bad practice to use $rootScope as global event bus in controllers (they have access to child scopes). This is basically an antipattern that may cause memory leaks. Considering that an event was $broadcasted, it will propagate to child scopes. As a rule of thumb, it should be $scope.$on(...), unless there are reasons why it should be done on $rootScope specifically.
This happens when the view does not get notified of a change in controller. It usually happens for ng-repeat arrays/objects or inside the forms that do not use a global object. The $timeout solution works or you can call $scope.$apply(). Please look at this document for more information:
https://www.sitepoint.com/understanding-angulars-apply-digest/

Understanding specific $scope.$apply(fn) implementation

What does the following function wrapped by scope.$apply do? I can't seem to find the answer to this, but I see examples where it is used in directives.
scope.$apply(function() {
fn(scope, {
$event: evt
})
});
The closest explanation I could find implies that this might be used when the event that you want to respond to is not handled by Angular directives. Here is the explanation I am referring to.
If someone could provide the intended use of this pattern and what it means, it would be appreciated.
EDIT 1
I should have seen this earlier. Must need more sleep.
Since my example is not a complete working one. It makes a lot more sense after looking at the referenced explanation carefully.
The fn(scope, {$event: evt}) call is invoking the parsed reference to a custom function via the directive parsed in the following line:
var fn = $parse(tAttrs.myContextmenu);
So the target function implementation is capturing a specific event via the directive and then suppressing it.
So I guess this is useful when you do not want to clutter directives with controller specific functions and maybe fire a different event in response to another event then let a controller handle it.
scope.$apply is used to manually trigger Angular's digest cycle for any async events that happen outside of Angular's execution context.
One such async event is element.on("click", function(e){...}) (or any other event related captured with .on), but could also be other async function outside of Angular context.
The second part is an invocation of the "$parsed" expression. It accepts a scope as a parameter and a map of "local" variables, such as {$event: evt}. The intent is every similar to what scope: "&" is doing - but without creating an isolate scope. For example, if the expression is:
<my-directive p="doSomething(foo)">
then, if doSomething(foo) is $parsed, the caller can supply the value of foo:
var parsedFn = $parse(attrs.p);
parsedFn(scope, {foo: 5})'
This will cause the invocation of doSomething(5)
Posting an answer to this since it makes sense to me (see my edit).
The fn(scope, {$event: evt}) call is invoking the parsed reference to a custom function via the directive parsed in the following line:
var fn = $parse(tAttrs.myContextmenu);
So the target function implementation is capturing a specific event via the directive and then suppressing it.
So I guess this is useful when you do not want to clutter directives with controller specific functions and maybe fire a different event in response to another event then let a controller handle it.

Does using .on to bind click benefit from ngTouch?

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.

When is it safe to use $scope.$apply()?

I guess the title is pretty much clear what I am asking. I have created this fiddle : http://jsfiddle.net/Sourabh_/HB7LU/13142/
In the fiddle I have tried to replicate an async scenario. This is just an example but in an AJAX call if I don't use $scope.$apply() the list does not get updated. I want to know if it is safe to use $scope.$apply() every time I make an AJAX call to update a list or is there some other mechanism I can make use of?
Code I have written to replicate the scenario(same as in fiddle):
HTML
<div ng-controller="MyCtrl">
<li ng-repeat="item in items">
{{item.name}}
</li>
<button ng-click="change()">Change</button>
</div>
JS
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.items = [{name : "abc"},{name : "xyz"},{name : "cde"}];
$scope.change = function(){
test(function(testItem){
$scope.items = testItem;
//$scope.$apply();
})
}
function test(callback){
var testItem = [
{name : "mno"},
{name : "pqr"},
{name : "ste"}
];
setTimeout(function(){callback(testItem)},2000);
}
}
Edit It was not clear the OP was trying to mock a backend call. Even so, using the $timeout service is a great way to avoid the need of calling $scope.$apply manually and is a more generally applicable solution than using a Promise (in cases where you're i.e. not calling $http it doesn't always make sense to force your changes into the next cycle by wrapping them with a Promise).
Update your code to use the $timeout service and it should work without having to call $apply.
$timeout is a wrapper around the native setTimeout with an important difference: $timeout will delay the execution at least until the next $digest cycle runs.
So passing in no delay will still delay the execution up until the next cycle. Passing in 2000 will delay the execution up to the next cycle after 2000ms.
Hence, this is an easy trick to make sure your changes are picked up by Angular without ever having to call $apply manually (which is considered unsafe in any case)
function MyCtrl($scope, $timeout) {
$scope.items = [{name : "abc"},{name : "xyz"},{name : "cde"}];
$scope.change = function(){
test(function(testItem){
$scope.items = testItem;
//$scope.$apply();
})
}
function test(callback){
var testItem = [
{name : "mno"},
{name : "pqr"},
{name : "ste"}
];
$timeout(function(){callback(testItem)},2000);
}
}
If you want to immidate an API-Rest-Call, use the returned promise in your Controller instead setting the scope inside the Rest-Call.
$http.get('uri')
.success(function(data) {
$scope.items = data
});
Avoid using $apply(). From the Angular GitHub Repo:
$scope.$apply() should occur as close to the async event binding as
possible.
Do NOT randomly sprinkle it throughout your code. If you are doing if
(!$scope.$$phase) $scope.$apply() it's because you are not high enough
in the call stack.
To your question:
If you find yourself in a situation where you need $apply(), rethink your structure.
Just for safety reason: Never use $apply()
You need to use $apply every time you use something that is not "angular way", like Anzeo told about $timeout.
For example if you use jQuery's http instead of angular's $http, you will have to add $scope.$apply.
The $apply, should be used when the code is not executed in a angular digest loop. In normal circumstances we will not need to use it, but we might have to use it if we have a code that is called from a jQuery event handler or from methods like setTimeout(). Even if you have a function that is called from another angular function like a watch or angular event handlers you need not use $apply() as those scripts are executed in the digest cycle.
One safe way is to check the $scope.$$phase param before calling $scope.$apply() like
if($scope.$$phase){
$scope.$apply();
}
In your case but you can use $timeout as suggested in the another answer
What is $$phase in AngularJS?
Why is using if(!$scope.$$phase) $scope.$apply() an anti-pattern?
All the above answers give some information but they did not answer few doubts I had or at the least I did not understand. So I'm giving my own.
It is very clearly stated in angular docs when to use $apply
the call backs of $http or $timeout or ng-click, ng-..... have $apply() wrapped in them. Therefore when people say you don't have to use $apply() when you do angular way of doing things, this is it. However, one of the answers mentions angular event handlers are also wrapped with $apply(). This is not true or by that the user means just ng-click kind of events (again ng-....). If the event is broadcast on rootScope (or any scope for that matter) outside of $http or $timeout or ng-click, for eg: from a custom service, then you need to use $apply() on your scope although $rootScope.$broadcast is also angular way of doing things. in most of the scenarios we will not need this because the state of the app changes when something happens. ie, clicks, selection change, etc... and these are in angular terms are already using $apply() when we use ng-click ng-change respectively. Handling server side events using signalr or socket.io and while writing custom directives where the necessity to change just the directive's scope are few examples where it is very essential to use $apply()
As #gruberb pointed out in the comments, if you tried to mock a REST call, you better use a promise than $apply.
For this you need to use $q service to create and return a promise. Then simply call it and work with you result by calling then() method on the returned promise.
function MyCtrl($scope, $q) {
$scope.items = [{name : "abc"},{name : "xyz"},{name : "cde"}];
$scope.change = function(){
test().then(function (items) {
$scope.items = items;
$scope.$apply();
});
};
function test() {
var defered = $q.defer();
var testItem = [
{name : "mno"},
{name : "pqr"},
{name : "ste"}
];
setTimeout(function() {
defered.resolve(testItem);
},2000);
return defered.promise;
}
}
A better way is to use $scope.$applyAsync(); instead of $scope.$apply();
The reason to avoid use of $scope.$apply() is given here:
Error: [$rootScope:inprog] digest in progress. Fix

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

Categories