Angular is loosing my my JSON values, and i'm trying to find out why. I think I need to make a promise or a time out, or maybe apply. I'm not quite sure...
angular version: 1.2.1
galleryApp.controller('GalleryCtrl', function ($scope, $http, $routeParams, $filter){
$http.get('mygallery.json').success(function(data) {
$scope.gallery = data;
//DISPLAY: CONTENTS OF JSON IN OBJECT : WORKS
console.log($scope.gallery);
});
//DISPLAY: undefined : DOES NOT WORK AS EXPECTED
console.log($scope.gallery);
//DISPLAY: CONTENTS OF OBJECT: I can see scope.gallery exists!
//I just can't seem to access scope.gallery directly outside of http.get()
console.log($scope);
});
Note: scope.gallery or "gallery" works perfectly fine in my view! {{gallery.name}} etc.
It seems like there is some behind the scenes things angular is doing to the scope, or some concept that i'm not quite grasping.
Well, it's trivial as $http.get is an asynchronous operation. So while it is working the rest of the code will be finished. If you log the $scope.gallery it is undefined yet. If you log the $scope it's still undefined but will be updated when the success callback is invoked. Th reason of this effect for you is just feature of console.log which writes not the current snapshot but the object itself so if changed the output of the console will be updated respectively. But in general none of your code outside of the $http.get will work as you expected here. So you should either use the success callback or use $watch to track the changes.
Also refer to this documentation: $http, $q
The result of http.$get is a promise (specifically an HtmlPromise, see the $http docs). A promise is described as the following in the $q docs:
an interface for interacting with an object that represents the result of an action that is performed asynchronously, and may or may not be finished at any given point in time
The success method of an HtmlPromise takes a callback function to run once the promise is resolved (meaning the action is complete). The anonymous function thus runs asynchronously. When it is logged outside of the callback, the callback has not yet been run and therefore the scope variable has not been set.
I imagine the view is as expected because the http request completes and the view is updated so fast, it is imperceivable.
Related
The problem: Using a this.promise.then(function(){}) function inside a controller method seems to create a $rootScope:infDig loop although the returned value of that method does not change. It's worth noting that the infinite digest loop is gone when removing the .then() line (see code).
The code:
{{ getSelectedProjects() }} in the view and the following method in the attached controller
var vm = this;
vm.getSelectedProjects = function() {
if (angular.isUndefined(vm.hello)) {
vm.hello = "hello"
vm.promise = $q.when("hello i am a resolved promise")
}
vm.promise //In my original code, this promise is returned by a service (MyService.getPromise()). The resolved value of the returned promise may change which is why the service needs to be called at each digest cycle.
.then(function (ans) {
vm.hello = ans;
}); //remove .then(...) function and there is no infinite digest loop anymore
return vm.hello;
}
The question: Why is this creating an infinite digest loop although the returned value does not change ? How can I avoid it (I badly need the result of that promise)?.
My shaky thoughts: I guess it has to do with the fact .then() each time returns a new promise. The documentation does not state it, but if that promise is each time attached to the controller object (vm), this might be considered as a state change of the controller and trigger a new digest loop, which re-evaluates vm.getSelectedProjects() and again attaches a new promise to the controller, etc.
EDIT
My hypothesis in "shaky thoughts" does not seem fully correct or complete, as the problem persists with this code:
vm.getSelectedProjects = function() {
if (angular.isUndefined(vm.hello)) {
vm.hello = "hello"
}
$q.when("hello i am a resolved promise")//this object is not attached to the controller object
.then(function () {});
return vm.hello;
}
Based on the problems encountered and the comments we created coding guidelines for working with promises in our project. Found it interesting to share it as I haven't seen this documented anywhere.
Coding guideline: Working with promises
Use of promises
Promises are amazing objects, however a wrong use can create headaches. Therefore we have a few guidelines :
Do not use promises in views
Do not use promises in controllers, except in functions called by event handlers (click, init, etc.), the controller construction function (=executed only once) or in animations
If a promise result is used in a view, the model (or the manager) should contain a property that is empty by default and set to the resolved value when the promise is resolved. The controller just returns that value.
Implementation
This is the kind of structure that should be in the model, manager, or other service, as close as possible to the data source. NOT in controllers :
var myApiMethod = function() {
//We call "caching" the fact that this method is executed only if $scope.item is undefined.
If (angular.isUndefined($scope.item) {
$scope.item = {}
$http(xxx).then(function(value){
$scope.item = value;
});
return $scope.item
}
}
The controller then just returns the answer of myApiMethod. You might need to check if 'myService.myApiMethod() != {}'.
In the service we can define a caching behaviour (ex: retrieve data from backend every 2 hours, at a certain event clear the cache, etc. ). As long as there is only one single source of truth (coming from the service in this case, not from the controller) concerning the object to be returned you can easilly play around with this.
Argumentation
*Why not to use promises in views or in controller methods called by expression evaluations (e.g., {{ vm.getMyApiMethodResult() }} *:
Including promises in views is a feature that has been desactivated in Angular 1.x
If myApiMethod is put in the controller without the caching and then used in an expression evaluation (e.g., {{ myCtrl.getMyApiMethod() }} ), it will create an infinite digest cycle, because the resolution of the promise will trigger a new digest cycle, that will again call the vm.getMyApiMethod() function, etc. (more info in this SO answer )
Why not put myApiMethod in t he controller with the caching (like implementation above) to make sure myApiMethod is called only once and avoid the infinite digest loop? Three reasons:
if the response of myApiMethod() changes into a new object, $scope.item will not be updated.
Another drawback of this (that we used a lot in the past, you might still see wrong implementations in the code) is that it creates a lot of duplicate code as we need to duplicate this quite heavy code structure (that can become much more complex when several promises are involved) in all controllers calling myApiMethod()
If you "cache" in the controller, you might end up with different controllers returning different answers although they both return the answer from the same service 'myApiMethod' but at different moments in time.
Remember : This guideline only applies to controller functions called in expression evaluations. You might need promises in controller functions called by event handlers (click, etc.) and that's fine because these functions are not called at each of angular's digest cycles.
I have a download function, which has a promise called onProgress(which returns download percentage), I want to go to some other view and come back. Even though controller's scope is with proper data the page is not getting refreshed with new data.
Even with this vague description it sounds like the onProgress() function is not in "angular world". Therefore, any changes to $scope that you make in the promise callback will not be noticed by angular until the next digest cycle.
The solution is easy: inside the onProgress() promise callback, wrap your changes to $scope in $apply:
onProgress().then(function(percent)
{
$scope.$apply(function() {
// your changes to $scope here, so that angular notices them
}
}
In my AngularJS Controller, I have a $scope.init method that gets called when the controller gets initialized. $scope.init calls $scope.loadData and $scope.loadNames, which each fire an HTTP GET Request to an API to fetch some data.
I have written some unit tests using $httpBackend to mock the API responses and test $scope.loadData and $scope.loadNames. Those work fine but something that I can't seem to test is the $scope.init method. Is there a way to mock or stub a method that gets called on controller initialization? What are the best practices for this?
You can verify that the scope objects changed in $scope.init have the proper values after the controller initialization. You can even try to change the values of those objects, and rerun the init method, and check that everything matches afterwards
You can test:
the state of the controller is as expected after it is "initialized"
verify that loadnames and loaddata gets called (jasmine's spyOn+toHaveBeenCalled)
play with a promise object mocking the loadnames/loaddata methods response to verify your behavior under unexpected conditions
As my app initializes, the call to the api happens:
.run(function($ionicPlatform, $http, $localstorage, $model) {
$http.get($model.apiurl).success(function(data) {
$localstorage.setObject('data', data);
// reload template here!
});
})
When the api call has succeeded and the localstorage object is set, I want to reload my template (tab-categories.html) so the data can be displayed. How do I do this, ngRoute, stateProvider, ... ?
You might be missing the point of angular if you ask this question. If your template has values which are bound to a model, then changing those values will automatically update the view on the next digest. It is possible that your asynchronous code (the request) does not trigger a digest, in which case you will have to do it manually. There are many ways to do that: digest and apply
One simple way is to inject $timeout, and do a zero duration timeout (no time argument) with the sensitive code in the body of the function you pass in
Edit: so to answer your question more directly, you should be storing your data somewhere in your application when the call succeeds, and then rely on the angularjs digest loop to update your view. That's one of angulars big work saving features.
Use $route.reload(); method to reload entire page after your successful Transaction, be sure to add dependency injection '$route' in your Controller.
I posted an issue on the AngularJS github but it doesn't seem to be getting a whole lot of attention and I wasn't able to fix it myself since it's a pretty low-level issue, so I think it's time to look for a workaround.
Angular allows you to put a promise (or anything with a .then(...) function) into your scope, and once it is resolved, all $watches and anything bound to that promise will use the resolved value. The issue arises when you use a function to return a promise, as the same doesn't apply - it's handled like a plain object.
For example:
var helloDef = $.Deferred();
$scope.hello = helloDef.promise();
$scope.getHello = function() {
return $scope.hello;
};
$timeout(function() {
helloDef.resolve('Hello!');
}, 1000);
Fiddle
Here using ng-bind="hello" works fine and outputs Hello!, but ng-bind="getHello()" outputs [object Object] as the internal $watch returns the promise object. Works the same way with $q instead of $.Deferred.
In my actual code I'm creating the promise the first time the function is called, so I can't just pre-make the promise and refer to that in scope.
I also need it for more than just ng-bind, so making my own binding directive which handles this case properly isn't viable.
Anyone have any ideas? I'm currently returning the promise if the data hasn't resolved and the actual result if it has, but that's a pain to work with. Anything bound to the data briefly causes weird side effects as the data is being loaded, like ngRepeat using the promise object instead of the resolved value to create elements.
Thanks.
UPDATE: Pull request: https://github.com/angular/angular.js/pull/3605
UPDATE 2: For future reference, automatic promise unwrapping has been deprecated in the 1.2 brach.
For some things, I use $resource. If I need to wait for it, $then works well:
var r = $resource...get...
var p = r.$then...
Otherwise, I build my own resource-like object that is not a promise, but has a promise that I can wait for.
Pull request merged, it should be fixed in 1.2.0 so I'll mark this as the answer.