executing Angular JS code after some jQuery.ajax has executed - javascript

I want to update an Angular scope with data returned by some jQuery ajax call. The reason why I want to call the Ajax from outside Angular is that a) I want the call to return as fast as possible, so it should start even before document.ready b) there are multiple calls that initialize a complex model for a multiple-page web app; the calls have dependencies among themselves, and I don't want to duplicate any logic in multiple Angular controllers.
This is some code from the controller. Note that the code is somewhat simplified to fit here.
$scope.character = {};
$scope.attributeArray = [];
$scope.skillArray = [];
The reasoning for this is that a character's attributes and skills come as objects, but I display them using ng-repeat, so I need them as arrays.
$scope.$watch('character',function(){
$scope.attributeArray = getAttributeArray($scope.character);
$scope.skillArray = getSkillArray($scope.character);
});
In theory, when $scope.character changes, this piece of code updates the two arrays.
Now comes the hard part. I've tried updating $scope.character in two ways:
characterRequestNotifier.done(function() { // this is a jQuery deferred object
$scope.$apply(function(){ // otherwise it's happening outside the Angular world
$scope.character = CharacterRepository[characterId]; // initialized in the jquery ajax call's return function
});
});
This sometimes causes $digest is already in progress error. The second version uses a service I've written:
repository.getCharacterById($routeParams.characterId, function(character){
$scope.character = character;
});
, where
.factory('repository', function(){
return {
getCharacterById : function(characterId, successFunction){
characterRequestNotifier.done(function(){
successFunction( CharacterRepository[characterId] );
});
}
};
});
This doesn't always trigger the $watch.
So finally, the question is: how can I accomplish this task (without random errors that I can't identify the source of)? Is there something fundamentally wrong with my approaches?
Edit:
Try this jsfiddle here:
http://jsfiddle.net/cKPMy/3/
This is a simplified version of my code. Interestingly, it NEVER triggers the $watch when the deferred is resolved.

It is possible to check whether or not it is safe to call $apply by checking $scope.$$phase. If it returns something truthy--e.g. '$apply' or '$digest'--wrapping your code in the $apply call will result in that error message.
Personally I would go with your second approach, but use the $q service--AngularJS's promise implementation.
.factory('repository', function ($q) {
return {
getCharacterById : function (characterId) {
var deferred = $q.defer();
characterRequestNotifier.done(function () {
deferred.resolve(CharacterRepository[characterId]);
});
return deferred.promise;
}
};
});
Since AngularJS has native support for this promise implementation it means you can change your code to:
$scope.character = repository.getCharacterById(characterId);
When the AJAX call is done, the promise is resolved and AngularJS will automatically take care of the bindings, trigger the $watch etc.
Edit after fiddle was added
Since the jQuery promise is used inside the service, Angular has no way of knowing when that promise is resolved. To fix it you need to wrap the resolve in an $apply call. Updated fiddle. This solves the fiddle, I hope it solves your real problem too.

Related

Can I access $timeout without using the DI container?

In AngularJS, can I access $timeout without resorting to using the DI container?
Edit:
For those asking for "why". I am using an older version of AngularJS and want to create a utility function that will perform a digest asynchronously.
The intention being that I can place logic inside a promise then for execution after a digest has occurred and the UI has taken into account the model change.
I do not want client code to have to use the injector to use said function.
I wanted something like this:
my-file.js
//...
model.watchedProperty = 'new value';
// Now I want to wait for a digest to occur so that I can ensure the UI is updated before proceeding...
digestAsync(localScope)
.then(function() {
// continue...
});
// ...
digest-async.js
function digestAsync(scope) {
return $timeout(function() { // I don't want to have to use the injector...
scope.$digest();
});
}
You can manually get the injector and then get the $timeout service.
var $injector = angular.injector(['ng']);
var $timeout = $injector.get('$timeout');
If you don't want to inject $timeout you can add $injector as a DI, and in your code you can put this:
$timeout = $injector.get('$timeout');
No, you can not. A lot of angular is itself written in angular, including $timeout. So you can access it in any way you can access any other self-written service - by Dependency Injection
You only need to use $timeout if you want the callback function to be executed inside an Angular digest cycle, and if you pass 0 as the interval then it will be executed in the next digest.
The setTimeout function from JS will execute the callback using the next "thread" cycle. That means that the current thread has to terminate first before the callback can be execute. That doesn't mean that the next thread cycle will also be an Angular digest.
This doesn't matter in your example because you are forcing a $digest, except that you should be using $apply and not $digest.
I think what you are trying to do is create a promise that resolves inside an Angular digest. That's not really the proper use for promises because a digest is not a resource to be resolved.
I think you can skip everything related to the $timeout and just use $apply as it was designed.
localscope.$apply(function(){
// do digest work here
});
That is the same as the following.
$timeout(function(){
// do digest work heere
},0);
Both can be executed outside of Angular, and both will execute the callback during the next digest cycle. $apply will call $digest if needed and it does state this in the documentation.
For times when you don't know if a $digest is in progress.
/**
* Executes the callback during a digest.
*
* #param {angular.IScope|angular.IRootScopeService} $scope
* #param {function()} func
*/
function safeApply($scope, func) {
if ($scope.$$phase) {
func();
} else {
$scope.$apply(function () {
func();
});
}
};
Since you plan to use it outside the app, there is zero chance that you will stumble upon infamous '$digest already in progress' error. Why? Because $digest isn't asynchronous process, more like the opposite of it. All that $digest() function does is calculating current scope state in loop - no promises, no timeouts.
That's exactly what
Don't do if (!$scope.$$phase) $scope.$apply(), it means your
$scope.$apply() isn't high enough in the call stack.
well-known statement refers to. The only time when 'already in progress' will happen is when $digest is triggered during $digest or within $apply, e.g. when outer JS function is used as Angular callback. This indicates poor application design and should be treated accordingly.
Thereby $digest function can be exposed to window
app.run(function ($rootScope) {
window.$digest = angular.bind($rootScope, $rootScope.$digest);
});
And used in synchronous manner. No promises. No timeouts.
model.watchedProperty = 'new value';
$digest();
// 'watchedProperty' watcher has been digested ATM
And I assume that you already know why mixing Angular and outer code like that is considered a bad practice and should be avoided.

AngularJS don’t get JSON data

I want to get data from a JSON file by using $http.get with AngularJS.
I use the "new way" to write AngularJS scripts (link).
PLUNKER
Nothing happens, Firebug tells me nothing.
I think the problem is in my activate() function code, I don’t really understood the promises.
function activate() {
return $http.get('test.json').then(function(data) {
vm.result = data;
return vm.result;
});
}
Have you an idea about this ?
I see a couple of problems.
First, in your plunker code you have:
controller.$inject = ["$http"];
function controller() {
You are missing the $http parameter in your controller function signature.
function controller($http) {
Once I fixed that, I found your index.html page was binding to {{c.value}}, but your controller never defines a value property. Maybe this should be {{c.result}}? If I make these changes I get a visible result.
You're not able to return at that point in your then callback. Simply return the $http call itself, of which will be a promise, and resolve it. Also, your console should be telling you something like $http is undefined since you did not inject this service properly. Observe the following...
function activate() {
return $http.get('test.json')
}
[...]
activate().then(function(response) {
vm.result = response.data;
});
Plunker - demo
Side note - you'll likely want to wrap activate() into an reusable service to keep this logic out of our controllers, so we'll instead inject the defined service into controller instead of $http directly.
Plunker - demo with this logic wrapped in a simple service

Angular $scope values set in reverse order

I have been working on Angular for one month now. I have been struggling with scoping and keeping state between views.
I don't know how to debug in angular, so I use the console to track the values of the scope when a view is loaded.
This is one of my controllers:
.controller('LegDetailCtrl',function($scope,$stateParams, LegService,ControllerService){
$scope.reservation = ControllerService.getReservation();
LegService.getLeg($stateParams.legId).then(function(data){
ControllerService.setLeg(data.data);
$scope.leg = data.data;
console.log('Check leg',angular.toJson($scope.leg));
});
$scope.seats = LegService.getSeats();
$scope.pax = LegService.getPax();
$scope.user = ControllerService.getUser();
$scope.reservation.leg = $scope.leg;
$scope.reservation.client = $scope.user;
$scope.reservation.pax = $scope.pax;
console.log('Check scope leg', angular.toJson($scope.leg));
})
As I understand, in JS the execution is made in order from top to bottom (not sure of this). I think this way, I am processing and then setting the $scope.leg value, then I use it to feed the $scope.reservation object.
To me, the correct console output would be:
log Check leg, {aJsonObject}
log Check scope leg, {anotherJsonObject}
But what I get is this:
log Check scope leg,
log Check leg, {aJsonObject}
So, it looks like it sets all the values to the scope and then, executes the LegService.getLeg() method.
How do I make this to run in the correct order?
Thanks in advance.
If you're using chrome there's a great debugging tool for AngularJS apps called Batarang
.
To solve your problem you can chain your promises like below.
function LegDetailCtrl($stateParams, LegService, ControllerService){
var vm=this;
vm.reservation=ControllerService.getReservation();
LegService
.getLeg($stateParams.legId)
.then(function(data){
ControllerService.setLeg(data.data);
vm.leg=data.data;
return data.data;
})
.then(function(data) {
var user=ControllerService.getUser();
var pax=LegService.getPax();
var seats=LegService.getSeats();
vm.seats=seats
vm.pax=pax
vm.user=user
vm.reservation.leg=vm.leg;
vm.reservation.client=user
vm.reservation.pax=pax
});
}
LegDetailCtrl.$inject=['$stateParams', 'LegService', 'ControllerService'];
angular
.module('yourModule')
.controller('LegDetailCtrl', LegDetailCtrl)
The .then( in the following line
LegService.getLeg($stateParams.legId).then(function(data){
is an asynchonous call to the function inside the then-block (which contains the "Check leg")
The execution is deferred till the event loop of javascript is empty (javascript is only single threaded).
That means that the code of the main function is executed first (result is "Check scope leg")
and after that the async call inside the then-block is executed ("Check leg")
If you ran this code outside of the browser, you'd find that the console.log call within the callback you passed to then is never called at all. That is because the callback is not called until the promise returned by getLeg is resolved, and that won't happen until the next angular $digest cycle.

How can I get an asynchronous service call to update the view when it returns in AngularJS?

This is a follow up to my previous AngularJS question.
I am trying to replicate the functionality of the $resource service that is mentioned in this video tutorial: link.
In this tutorial, it is shown that you can update a scope variable and its view using an asynchronous call. First, here's the resource that's set up:
$scope.twitter = $resource('http://twitter.com/:action',
{action: 'search.json', q: 'angularjs', callback: 'JSON_CALLBACK'},
{get: {method: 'JSONP'}});
Then the get function is called on the resource to make the ajax call and update the necessary scope variable:
$scope.twitterResult = $scope.twitter.get();
What's happening here is that $scope.twitter.get() immediately returns an empty object reference. Then, when the ajax request returns, that object is updated with the data from Twitter. The view then updates all {{twitterResult}} instances and you see the data that was returned.
What I don't get is, when the object is updated in the asynchronous callback, how does the scope know that the value has changed?
I don't think it does - the callback must call some kind of update function on the scope, right? But if so, how is this done? I have heard of a function called $apply that might be the answer - but how do you reference $scope.$apply() from inside the resource?
I have created a JSFiddle to illustrate the problem: http://jsfiddle.net/Qcz5Y/10/
(Edit: Here is a newer JSFiddle with a different async call to illustrate the problem using an ajax call instead of setTimeout: http://jsfiddle.net/Qcz5Y/14/)
In this example, you will see a controller and a resource. The controller's scope has a property, fruit, which starts out set to an object. When you click 'Get new fruit', that property is set to fruitResource.get(), which immediately returns an empty object. It then makes an asynchronous call, and finally executes a callback which updates the value of the object that was originally returned.
When this happens, the value of $scope.fruit is properly updated to what was set in the callback. But it isn't reflected in the view.
If you click "Print current fruit", it will log to the console the current value of $scope.fruit. This was just for checking; it has the side-effect of updating the view.
I feel like I need to be doing something like $scope.$apply() at the bottom of the get_callback function (you'll see it in the code). Would that be the right thing to do? If so, how do I get a reference to $scope inside the fruitResource? I know I can probably pass $scope into fruitResource.get() as a parameter and then reference it that way, but the $resource.get() function in the example mentioned at the top doesn't need to do this, so I was hoping that AngularJS provides some way to get the scope from services automatically.
Thanks!
I figured out that you can pass $rootScope into a service and then call $apply on that. So when you want to update your view, you just do $rootScope.$apply().
Here is the updated JSFiddle: http://jsfiddle.net/Qcz5Y/11/
Actually simulating behavior of Resource is quite simple and boils down to doing async call and returning an empty results immediately. Then, when data do arrive from the server a returned reference needs to be updated. Here is the example using your use-case:
get: function() {
var fruit = {};
// Asynchronous call that executes a callback. Simulation of ajax/db request
$timeout(function() {
fruit.type = ['banana', 'strawberry', 'watermellon', 'grape'][Math.floor(Math.random() * 4)];
fruit.count = [1, 2, 3, 4][Math.floor(Math.random() * 4)];
}, 2000);
return fruit;
}
Here is the complete jsFiddle: http://jsfiddle.net/pkozlowski_opensource/sBgAT/1/
Please note that I'm using angular's $timeout service to simulate async call since $timeout will take care of calling $apply when it is needed and thus will trigger UI re-paint without any manual intervention. You were bumping into issues since you were trying to use setTimout that needs a call to $apply.

What's a simple way to define multiple jQuery.Callbacks() prerequisites?

What's the simplest way to define multiple jQuery.Callbacks() prerequisites?
// simple pubsub example
var pubsub=(function() {
var callbacksObj={};
return function(id){
if(!id) throw 'callbacks requires an id';
return (callbacksObj[id] = callbacksObj[id] || $.Callbacks('unique memory'));
};
})();
function fn1(){
console.log('fn1');
};
function fn2(){
console.log('fn2');
};
function fn3(){
console.log('fn3');
};
// subscribing
pubsub('foo').add(fn1);
pubsub('bar').add(fn2);
pubsub('foo','bar').add(fn3); // adding a function with multiple dependencies
// publishing
pubsub('foo').fire() // should only log 'fn1';
pubsub('bar').fire() // should log both 'fn2' AND 'fn3' since both have fired
I can see wrapping each added function in another function that checks each id's fired() state, though this seems like a common enough scenario that perhaps there's a simpler way I'm missing.
I think deferred is what you're looking for:
http://api.jquery.com/category/deferred-object/
it looks like this:
$.when(some_promise).then(some_callback)
And you can have:
$.when(some_promise, another_promise).then(some_callback)
In this case some_callback will only be called if some_promise and another_promise have been resolved.
deferred basically just adds a level of abstraction to your asynchronous functions, making it easier to express the dependencies. I suppose your example would look like:
// simple pubsub example
var pubsub=(function() {
var callbacksObj={};
return function(id){
if(!id) throw 'callbacks requires an id';
return some_assynchrnous_function(id); // like $.ajax
};
})();
function fn1(){
console.log('fn1');
};
function fn2(){
console.log('fn2');
};
function fn3(){
console.log('fn3');
};
// subscribing
var foo = pubusb('foo'); // should return a deferred/promise
var bar = pubsub('bar');
$.when(foo).then(fn1);
$.when(bar).then(fn2);
$.when(foo, bar).then(fn3);
I'm not entirely sure if this is correct for jQuery, but you get the idea. I didn't find the jQuery API to make very much sense to me so I wrote my own :3
I find it useful to be able to make 'empty' deferred objects, then attaching a done handler to it, then passing the deferred object along to something that will eventually end up resolving it. I'm not sure if jQuery can do this.
It may seem a little daunting at first, but if you can wrap your head around it you can get so much awesomeness from this. Dependencies is a big one but scoping is also great, you can add multiple done handlers on multiple levels, one handler may handle the actual data, one handler may just be interested in when the handler finishes so you can show a loading bar etc.

Categories

Resources