Angular $scope values set in reverse order - javascript

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.

Related

Why does it work correct with an array but not with an object?

I am developing an AngularJS application and found the following behavior.
I have two functions in my service. The first function returns all the categories stored in the database and the second returns one category by its id.
Here is my service:
angular.module('categoriesRepository', [])
.service('categoriesRepository', ['$cordovaSQLite', 'sqliteHelper',
function ($cordovaSQLite, sqliteHelper) {
//this works - returns an array with all categories
this.getAll = function () {
var categories = [];
$cordovaSQLite.execute(sqliteHelper.getDb(),
"SELECT * FROM categories;")
.then(function (res) {
for (var i = 0; i < res.rows.length; i++) {
categories.push(res.rows[i]);
}
});
return categories;
}
//this works not - returns undefined
this.getById = function (id) {
var category;
$cordovaSQLite.execute(sqliteHelper.getDb(),
"SELECT * FROM categories WHERE id = ?;", [id])
.then(function (res) {
category = res.rows[0];
});
return category;
}
}]);
I know that I can use Angulars $q to run functions asynchronously, and use their values when they are done processing.
Why does the getById function return the category directly and the getAll wait until the array is filled?
EDIT
I had the getAll function posted wrong. There is no return statement before $cordovaSQLite.execute
UPDATE:-
After your question is updated.
In the first example your are creating an array first by doing var categories = [];and then returning this array before finishing your async call. When your async call completes it just pushes certain elements into the array thus not destroying the reference to the array (categories ) variable. When it is returned back if you will debug it you will find the function returning an empty array and later when the async call succeeds only then the array will be filled.
In the second example you are creating just a variable and then returning it before the async call finishes. But then the async call is finished you assign the variable to a new value. thus destroying the earlier reference.
Solution:-
Though not a preffered approach to make it work. you will have to maintain the category variable reference. for this you can use angular.copy OR angular extend
So the second part of your code should be like
this.getById = function (id) {
var category;
$cordovaSQLite.execute(sqliteHelper.getDb(),
"SELECT * FROM categories WHERE id = ?;", [id])
.then(function (res) {
angular.copy(res.rows[0], category);
//now the reference to the category variable
//will not be lost
});
return category;
}
Better Practice:-
The way you have been developing this application is wrong. Async calls should not be handled this way. I earlier asked a question just to clarify the way to handle the async calls and state inside the angular app, factories and controllers please have a look here. It provides two ways to handle the state and async calls. There might be many more practices out there but these two suit me best.
It is unfortunate that this approach appears to 'work' because it is caused by the modification of the returned array object "at some unspecified time" after it is returned.
In the usage the array is accessed/observed after1 it has been modified by the asynchronous call. This makes it appear to function correctly only because of the (accidental) asynchronous-later-than observation.
If the observation was prior to the actual completion of the SQLite operation - such as immediately after the getAll function call - it would reveal an empty array.
Both functions are incorrectly written and the first accidently releases Zalgo (or perhaps his sibling).
See How do I return the response from an asynchronous call? for more details.
1 Chrome's console.log can be confusing as it works like console.dir and thus may be showing the current value and not the value when it was invoked.
As stated already, this is a bad approach. You can't use result of your function immediately after it returns.
However I didn't see the answer to your exact question: why do they behave differently?
It happens because with an array you return a reference to an object (type Array). Later on you use same reference to modify contents of the object, i.e. push new items into the array.
However in second function you modify the reference itself. You make you local variable categories point to a new object. Thus old object (reference to which was returned to outer scope) remains untouched. To make it work the same way you should have written
category.row = res.rows[0];
You return the result of the execute in the first case, whereas you return the variable in the second case, which has most likely not been populated yet.

Variable scope when using $http service in AngularJS

I'm hoping someone can shed some light on the scope of variables when using the $http AngularJS service.
My code looks like this:
app.controller('TestPlanRequestCtrl', function($scope, $http) {
$scope.tableData = []; // Populate the table with this array
$scope.tpRequests = null;
$http.get('common/data/requests.json').success(function(data) {
$scope.tpRequests = data.TPRequests;
});
Next I want to run a loop to put my data into an array like so:
for (var i = 0; i < $scope.tpRequests.length; ++i) {
var requestObj= {
requestNum: $scope.tpRequests[i].RequestNumber;
}
$scope.tableData.push(requestObj);
}
This works great if the for loop is inside the function called from the success method, but I think it would be cleaner to keep it outside the call. If I have the loop outside the call, I get the error:
Error: $scope.tpRequests is null
I don't understand why tpRequests is populated in the get call, and then the data is gone after the get call ends. I'm guessing it is considering the $scope.tpRequests inside the function call to be a different one than the one I declared above the $http.get(). What's the correct way to do this?
You can use a $watch on $scope.tpRequests to do your data manipulation, however I would recommend just doing it in the success promise if there are no other ways to trigger the watch and simply check for null/undefined before operating.
A great primer on use cases for $watch and $watchCollection:
http://www.bennadel.com/blog/2566-scope-watch-vs-watchcollection-in-angularjs.htm

Can we save jquery POST/GET callback function's data and status in a variable for further use?

I want to save the value of data and status in a variable and use it after the closing brackets of jquery GET/POST function.But alert comes only when it is inside .get braces.
$(document).ready(function(){
$.get("demo_test.asp",function(data,status){
v = data;
});
alert("Data:"+v);
});
As Jasper said, your alert is being triggered before the request is complete (async!). So, you have two options:
Do your logic inside the callback:
$.get("demo_test.asp",function(data,status){
v = data;
alert("Data:"+v);
//Process stuff here
});
Or pass the received data onto another function and work with it there
$.get("demo_test.asp",function(data,status){
v = data;
doStuff(v);
});
function doStuff(param) {
console.log(param);
}
You're absolutely correct; the code is working as it should... here's why:
The page loads and starts running code, it then hits the .get command and then keeps running, obviously making it to the 'alert' you have next. Since the .get function is still working on fetching the data before your page makes it to the 'alert' part... there's nothing to prompt.
You might want to string things together after the .get, using deferred objects. Look into: http://api.jquery.com/deferred.always/
This is a way of tacking on another function inside of the one fetching your data, so they depend on each other.
Simple answer, yes, you can store the data in a global variable and access it elsewhere. However, you must wait until it is ready.
The better way to do it is to instead store the jqXHR globally and simply add a done callback to it when you need to access the data.
var reqDemoTest = $.get(...);
//... some time later...
reqDemoTest.done(function(data){
console.log(data);
});

executing Angular JS code after some jQuery.ajax has executed

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.

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.

Categories

Resources