How to update $scope immediately when dealing with non angular code? - javascript

I have to deal with an non angular library and need to create a comunication between them.
<div id="MoipWidget" data-token="{{personaltoken}}" callback-method-success="successCB" callback-method-error="errorCB"></div>
Every time that the page is loaded, I have to get a token from my server.
$http.post('https://example.org', obj)
.success(function(data){
$scope.personaltoken = data.token;
//Here I call the non angular library and I get and error telling me that the token is undefined.
//If I run the code from inside a $timeout works as I need...
})
.error(function(data){
alert('error');
});
I've also tried to run inside $scope.$apply but I get an error telling that $digest already in progress
The non angularjs library that I have to call is simple is just two lines.
var settings = {}
LibraryCall(settings);
How can I update the model immediately?

I've tried to use $scope.$evalAsync as #Kjell suggested but did not work.
After reading more about $scope, I've found what I needed.
$scope.$applyAsync(function(){
var settings = {}
LibraryCall(settings);
});
$scope.$applyAsync will schedule the invocation of $apply to occur at a later time.
https://docs.angularjs.org/api/ng/type/$rootScope.Scope

I removed the error callback for brevity, don't do it in your code :)
I suppose the code you call is asynchronous, if it's not, you should not have any $scope updating problem (because all angular promises call $apply already)...
This should work:
$http.post('https://example.org', obj).success(function(data){
$scope.personaltoken = data.token;
otherLibrary.doSomething(data.token, function(error, result) {
$scope.changeSomething = 'toHey';
$scope.$apply();
});
});
This shoud also work:
$http.post('https://example.org', obj).success(function(data){
$scope.personaltoken = data.token;
otherLibrary.doSomething(data.token, function(error, result) {
$scope.$apply(function() {
$scope.changeSomething = 'toHey';
});
});
})
This shoud raise the $digest already in progress error, because $http does wrap the promise callback on a $apply call already.
$http.post('https://example.org', obj).success(function(data){
$scope.personaltoken = data.token;
$scope.$apply(function() {
otherLibrary.doSomething(data.token, function(error, result) {
$scope.changeSomething = 'toHey';
});
});
})

Try using either $scope.$evalAsync() or $scope.$applyAsync().
They are made for stuff like this. It will execute the code later in time. Not that different from $timeout, but potentially faster.
$scope.$evalAsync(function(){
var settings = {}
LibraryCall(settings);
})
Edit: Just to quote Ben Nadel on the difference between $timeout and $evalAsync, from this post:
So, in essence, $scope.$evalAsync() combines the best of both worlds:
When it can (which is most of the time), it will evaluate your
expression in the same tick; otherwise, it will evaluate your
expression in a later tick, which is exactly what $timeout() is doing.

Related

AngularJS polling a $resource on an $interval doesn't automagically resolve the promise returned from $resource

So, the important bits:
When I bind a $scope variable in my controller directly to a $resource, angular's automatic promise resolution takes over and my view gets updated as soon as data comes back from the service. However, I need to poll the server for updates on an interval. Attempting to bind to the promise returned from the $interval call is not triggering angular's automatic promise resolution and I do not know if there is something that can be done to get this $interval working against a $resource.
Angular client, ASP.NET WebApi serving JSON, Angular $resource sitting over WebApi. Inside my angular controller, if I bind a $scoped variable to my $resource promises directly, I get results; however, I need my $resources to update on an $interval and I am having problems introducing the interval.
The working, "needs manual refreshing" code (the relevant part, at least)
'use strict';
var ctrls = angular.module('MyModule',[]);
ctrls.controller('FooCtrl', ['$scope', '$location','service',
function( $scope,$location,service)
{
$scope.Bars = service.Bars;
$scope.Baz = service.Baz;
}
var app = angular.module('MyModule.Services',['ngResource']);
app.service('service', ['$resource', function($resource)
{
var proxy = $resource(
'http://server/subsystem/api/Foo/:component',
{},
{
Bars: {method:'GET',isArray:false,url: 'http://server/subsystem/api/Foo/Bars'
,Baz: {method:'GET',isArray:false,rul: 'http://server/subsystem/api/Foo/Baz'
}
return proxy;
}]);
blah blah blah, ng-repeat in my view bound to "bar in Bars" = data to screen.
So now, I go into my controller to implement my interval, and I can get the interval working but the promise does not resolve.
The changes inside the controller (note that I added $interval and $document to my dependency list:
var stop;
$scope.Bars = {};
$scope.Baz = service.Baz; //Note that angular resolves this promise automatically.
$scope.pollService = function(){
if(angular.isDefined(stop))return;
stop = $interval(function(){
if(service){
$scope.Bars = service.Bars;
}
},1000);
};
$scope.stopPolling = function(){
if(angular.isDefined(stop)){
$interval.cancel(stop);
stop = undefined;
}
};
$scope.$on('$destroy',function(){
$scope.stopPolling();
});
$document.ready(function(){
$scope.pollService(); //when I attempt to execute my .then() on pollService(), I get "cannot
//call then on undefined"
});
Edit
Note that the problem I am having is that binding $scope.Bars directly to service.Bars triggers angular's automatic promise resolution but introducing the $interval call does not seem to be. Inspecting $scope.Bars in Chrome Dev Tools indicates that it is an unresolved promise.
I updated my sample code to match what I currently have in my debugger. I have several properties defined on my resource, and I included one that I named 'Baz'. If I assign $scope.Baz = service.Baz directly within my controller, the view binds to it when angular resolves the promise but the one on the interval never does.
You aren't returning the promise from $scope.pollService. Just add return stop; at the end of your function. Also note that the way your API is written, your function will still return undefined if stop is defined.
Therefore, it should probably look like:
$scope.pollService = function(){
if(!angular.isDefined(stop)) {
stop = $interval(function() {
if(service){
$scope.Bars = service.Bars;
}
}, 1000);
}
return stop;
};
stop is also not a very good descriptive name...
EDIT:
From my understanding after reading the docs, you should be invoking the action method on the service like $scope.Bars = service.Bars();. This will return an object that will eventually get populated with data once the promise is resolved. However, I think you also need to be careful here, because if the interval fires faster than the promise resolving rate you might run into issues. I think that you would be better off using $timeout.

If I can't use $apply() on $scope() what can I use?

In AngularJS, everywhere I read it says that you almost rarely ever call $apply() yourself, as it's a bad idea somehow (usually left unclear.)
Can someone explain to me:
How to avoid using $apply() but still updating variables in $scope
Why is it so terribly bad to call $apply()
And how to determine when it is OK to use $apply()
Example code:
app.controller('ExampleController', function($scope) {
$scope.variableForView = 'Initial value';
// for demonstration purposes
someEvent.on('eventFire', function(data) {
$scope.variableForView = data;
$scope.$apply(); // ???
});
});
Basically, $apply runs the $digest. If $digest is already running, then you can't initiate another $digest.
I have created the method $safeApply(), that I add to the prototype of my $rootScope inside the run() commend:
// $safeApply only calls $apply if $digest is not running
// Otherwise, will only call back fn()
Object.getPrototypeOf($rootScope).$safeApply = function(fn)
{
var phase = $rootScope.$$phase;
if (phase == '$apply' || phase == '$digest') {
if (_.isFunction(fn)) fn();
} else {
this.$apply(fn);
}
};
This basically makes sure you are not already in the $digest cycle before calling $apply. If you are (which means it is not necessary to call $apply, I just run the the fn. I never use $apply() and always use $safeApply().
Use the binding methods angular provides. Then it will do the $apply for you. The reason they say manually $applying is bad is because you should be using the angular methods for binding and if you are not using them and having to $apply manually, then your doing something wrong.
app.controller('ExampleController', function($scope) {
$scope.variableForView = 'Initial value';
// for demonstration purposes
$scope.$on('someEventFire', function(data) {
$scope.variableForView = data;
});
});
Also see https://github.com/angular/angular.js/wiki/When-to-use-%24scope.%24apply%28%29

Trigger $digest when variable changed by node.js function

I want to make an application with node-webkit using AngularJS.
No problem so far but when I want Angular to watch over a "legacy variable" by using the method described in this answer I just get an Error: [$rootScope:inprog].
I wrapped my node.js function into $scope.$apply() like this:
$scope.$apply(myNodeModule("test", function(err, res) {
if (err) {return console.log(err)}
myLegacyVar = res;
}));
And watching over the (global) myLegacyVar variable like described in the answer mentioned before:
function () {
return $window.myLegacyVar
}, function(n,o){
console.log("changed ",n);
$scope.data = n;
}
);
All of the code resides within my Angular controller function.
The function is async, so I guess it could have to do with that?
Or is this function inception I have going on in $apply maybe causing my error?
I'm just clueless right now, I have just recently started using AngularJS, hours of googleing haven't gotten my very far.
This answer helped. The safe $apply function there did the trick.
I added that function to my controller and added this snipped to my node.js function:
var scope = angular.element($("#angularDOMElement")).scope();
scope.safeApply(function(){
myLegacyVar = res;
})
I don't know if thats the proper way to do it but its working.

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

Angular's $rootScope.$digest

Reading this excellent book, Mastering Web Development in AngularJS, I ran across this code:
var Restaurant = function ($q, $rootScope) {
var currentOrder;
this.takeOrder = function (orderedItems) {
currentOrder = {
deferred:$q.defer(),
items:orderedItems
};
return currentOrder.deferred.promise;
};
this.deliverOrder = function() {
currentOrder.deferred.resolve(currentOrder.items);
$rootScope.$digest();
};
this.problemWithOrder = function(reason) {
currentOrder.deferred.reject(reason);
$rootScope.$digest();
};
My understanding is that the $rootScope.$digest(); calls are made in order to alert Angular that the Promise's state has been updated.
Is my understanding correct? Also, is it necessary to make the above $rootScope.$digest(); calls?
$scope.$digest() is what processes all of the $watch events that are on the current and children scope. It essentially manually tells the scope to check if a scope variable has changed. You don't generally want to use this when you are inside of a controller or a directive, because the $scope.$apply() function calls the $digest anyway and it is called when you mutate a scope variable.
Checkout this link for an example.
You don't need a $rootScope.$digest here because resolving/rejecting the promise will fire a $rootScope.$digest internally, as $interval,$timeout,and $http (after request finished) do that for you. And this $digest can throw errors of $digest already in progress.

Categories