AngularJS promises and ng-model - angularjs

Using this service to perform promises:
using this service to perform promises: Improving AngularJS Simple Factory - wrapper around PhoneGap Storage API
I can't figure out, why i am getting error when using ng-model directive and promises.
TypeError: Cannot assign to read only property 'favourite'
when clicking
<div ng-controller="DishCtrl">
<input type="checkbox" name="favourite" ng-model="dish.favourite" required smart-float ng-click="markFavourite(dish)"/>
</div>
and controller as follows:
angular.module('App')
.controller('DishCtrl', function($scope, NotificationService, MediaService, SQLService, $routeParams, $rootScope) {
var dishId = $routeParams.dishId;
SQLService.getDish(dishId).then(function(results) {
$scope.dish= results;
});
if i remove ng-model directive everything works. So promise is resolved correctly i quess.
I also tried to bind model directly - without promise and works.
Is it a bug, or did i missed something?
I am trying also resolving promises as example in here: http://jsfiddle.net/8HjgJ/

Did you try to initialize your scope variable before the promise is resolved. Something like that :
angular.module('App')
.controller('DishCtrl', function($scope, NotificationService, MediaService, SQLService, $routeParams, $rootScope) {
$scope.dish = false; //Default value, will be updated when promisse is resolved
var dishId = $routeParams.dishId;
SQLService.getDish(dishId).then(function(results) {
$scope.dish= results;
});

i dont understand why, but copying the object in Service:
Improving AngularJS Simple Factory - wrapper around PhoneGap Storage API
before resolving the promise helps!
function querySelectSuccess(tx, results) {
var len = results.rows.length;
var output_results = [];
for (var i=0; i<len; i++){
console.log('ts',results.rows.item(i));
var temp_copy = angular.copy(results.rows.item(i));
output_results.push(temp_copy);
}
deferred.resolve(output_results);
}

Related

Why is Angular not updating with a JSON file?

I'm trying to use a simple Angular JS app to load data from a JSON file to a website but it does not work.
The JSON file is:
{"a": "a"}
The Angular app is:
var app = angular.module("app", [])
.controller("ctrl", ["ser", function(ser) {
var vm = this;
ser.getInfo().then(function(data) {
vm.data = data;
});
}])
.service("ser", function() {
this.getInfo = function() {
return $.get("models/model.json");
};
});
The HTML is:
<div ng-controller="ctrl as ctrl">
<p>{{ctrl.data.a}}</p>
</div>
I'm not getting any console errors. I think the problem is related to the lexical scoping for the controller due to the asynchronous getInfo().then() call in the controller, I checked vm inside the function and it is being loaded correctly but doesn't seem to change the ctrl object or Angular is not updating when it does.
I'm serving the app locally.
It works sometimes but most times it doesn't. I can get it to work using $scope but I'm trying to figure out why it's not working now.
It appears you are using jQuery for the ajax. If you modify the scope outside of angular context you need to notify angular to run a digest
Change to using angular $http to avoid such issues
var app = angular.module("app", [])
.controller("ctrl", ["ser", function(ser) {
var vm = this;
ser.getInfo().then(function(response) {
vm.data = response.data;
});
}])
.service("ser", ['$http', function($http) {
this.getInfo = function() {
return $http.get("models/model.json");
};
}]);
DEMO
If it works with $scope that means that without it, Angular is not aware that you performed an asynchronous operation.
I think the following line is using jQuery: return $.get("models/model.json");
So even if you get your data from your function getInfo, it isn't synchronized with the view via vm.data = data;

Make data from factory http.get accessible to entire controller AngularJS

I am trying to give access to a json file that contains config information for my project (things like rev number, project name, primary contact, etc) I created a factory that retrieves the json file using http.get, I can then pull that data into my controller but I am unable to access it from anywhere in the controller.
I did not write the factory, I found it as an answer to another person's question and it is copied almost entirely so if it not the right way to accomplish what I am trying to do please correct me.
here is the factory:
app.factory('configFactory', ["$http", function($http) {
var configFactory = {
async: function() {
// $http returns a promise, which has a then function, which also returns a promise
var promise = $http.get('assets/json/config.json').then(function(response) {
// The then function here is an opportunity to modify the response
console.log(response.data.config);
// The return value gets picked up by the then in the controller.
return response.data.config;
});
// Return the promise to the controller
return promise;
}
};
return configFactory;
}]);
and here is my controller:
app.controller('footerController', ['$scope', '$rootScope', 'configFactory', function footerController($scope, $rootScope, configFactory) {
var body = angular.element(window.document.body);
$scope.onChange = function(state) {
body.toggleClass('light');
};
configFactory.async().then(function(d) {
$scope.data = d;
// this console log prints out the data that I am trying to access
console.log($scope.data);
});
// this one prints out undefined
console.log($scope.data);
}]);
So essentially I have access to the data within the function used to retrieve it but not outside of that. I can solve this with rootScope but I am trying to avoid that because I think its a bandaid and not a proper solution.
Any help would be great but this is my first experience with http.get and promises and all that stuff so a detailed explanation would be very much appreciated.
[EDIT 1] The variables from the config file will need to be manipulated within the web app, so I can't use constants.
Don't assign your response data to scope variable , create a property in your factory itself and assign the response to this property in your controller when your promise gets resolved.This way you will get the value in all the other controllers.
I have updated your factory and controller like below
app.factory('configFactory', ["$http", function($http) {
var configFactory = {
async: function() {
// $http returns a promise, which has a then function, which also returns a promise
var promise = $http.get('assets/json/config.json').then(function(response) {
// The then function here is an opportunity to modify the response
console.log(response.data.config);
// The return value gets picked up by the then in the controller.
return response.data.config;
});
// Return the promise to the controller
return promise;
},
config:'' // new proprety added
};
return configFactory;
}]);
app.controller('footerController', ['$scope', '$rootScope', 'configFactory', function footerController($scope, $rootScope, configFactory) {
var body = angular.element(window.document.body);
$scope.onChange = function(state) {
body.toggleClass('light');
};
configFactory.async().then(function(d) {
// $scope.data = d;
configFactory.config=d;
// this console log prints out the data that I am trying to access
console.log($scope.data);
});
// this one prints out undefined
console.log($scope.data);
}]);
Have you looked into using angular constants? http://ilikekillnerds.com/2014/11/constants-values-global-variables-in-angularjs-the-right-way/ You can leverage them as global variables accessible from any controller without the ramifications of assigning the values to rootScope

Why do I have to use $promise with $resource?

Admittedly I am relatively new to Angular but I've come unstuck using ngResource for a REST call.
I followed a factory pattern which seems to be relatively common.
However my I only get access to the REST response by using a $promise.then. Which I understand you do not require with $resource.
So I have this factory...
var LookupsService = angular.module('LookupsService', ['ngResource']);
LookupsService.factory('Lookups', ['$resource',
function ($resource) {
return $resource('http://localhost:5001/api/lookups', {});
}]);
And this fails (as undefined)
alert(Lookups.get().foo);
But this is fine
Lookups.get()
.$promise
.then(function (lookups) {
alert(lookups.foo);
});
I'm obviously missing something here. Some kind of schoolboy error ;-)
UPDATE
Thanks for all your help - it is now very clear. I gave #fikkatra the tick for the clarity of her answer and snippet. But pretty much all the answers were helpful. I must be careful using Alert for debugging!
Using alert on a promise won't work, i.e. it won't show the results within the promise. However, angular has been designed to use the $resource service directly to bind to the scope. This means you can bind a $resource result to a scope object, and angular will take into account that the scope object is a promise when applying the binding. That's why alert(resourceResult) won't work, but $scope.myObj = resourceResult; (and then bind it in a view), will work.
Inserted code snippet to explain things more clearly:
var app = angular.module('myapp', ['ngResource']);
app.controller('myctrl', function($scope, $resource) {
//this won't work:
//alert("won't work: " + $resource('https://api.github.com/users/fikkatra').get());
//but this will work
$resource('https://api.github.com/users/fikkatra').get().$promise.then(function(user) {
alert("will work: " + user.login);
});
//directly assigning $resource result to scope will work:
$scope.user = $resource('https://api.github.com/users/fikkatra').get();
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular-resource.js"></script>
<div ng-app="myapp" ng-controller="myctrl">
{{user.login}}
</div>
You don't have to, and shouldn't need to, use $promise with $resource. The $resource service can be used like this:
(function() {
'use strict';
angular
.module('app')
.factory('MyService', MyService);
MyService.$inject = ['$resource'];
function MyService($resource) {
return $resource('my/url/:id', {id: "#id"}, {
'query': { isArray: false },
'update': { method: "put" }
});
}
})();
It gives you back several HTTP methods that you can use in your code without you having to write them yourself. To use them you can do something like this:
function getActionTypes(){
MyService.query(
function success(result){
// do stuff with result
},
function failure(result){
console.error(result);
});
}
You actually don't have to use the $promise, it's just available if you want to.
Codepen: http://codepen.io/troylelandshields/pen/bpLoxE
angular.module('app', ['ngResource'])
.service('exampleSvc', function($resource){
return $resource('http://jsonplaceholder.typicode.com/posts/1', {});
})
.controller('exampleCtrl', function(exampleSvc, $scope){
$scope.result = exampleSvc.get();
exampleSvc.get().$promise.then(function(val){
//Do some other logic here on the val
$scope.promiseResult = val;
});
});
You can also assign the result of .get() to a variable on the scope and the result will be filled in when the request is resolved (in the first example above). This works well for assigning data to an object on your scope that will just end up being displayed on the page, but might not work well for other situations where you need to use the result of the .get() to do something else.
According to the specification you can pass in a success callback as outlined in this little code snippet.
var User = $resource('/user/:userId', {userId:'#id'});
var user = User.get({userId:123}, function() {
user.abc = true;
user.$save();
});
The success function callback will be called when the resource finishes.
Alternatively, you can use the promise syntax with get(), except you don't need to add your own promise as per your example. Instead you can simply do the following:
Lookups.get()
.then(function (lookups) {
alert(lookups.foo);
});
This is because get() returns a promise. When the promise is evaluated successfully then() will be called

Unexpected behaviors of promises

I've been facing a trouble while working with Factory/Service. I've created an AjaxRequests factory for all of my AJAX calls. My factory code is
.factory('AjaxRequests', ['$http', function ($http) {
return {
getCampaignsData: function () {
var campaigns
return $http.get(url).then(function (response) {
campaigns = response.data;
return campaigns;
});
}
}
}])
I've created another service in which I am injecting this factory. My service code
.service('CampaignsService', ['$rootScope', 'AjaxRequests', function ($rootScope, AjaxRequests) {
this.init = function () {
this.camps;
AjaxRequests.getCampaignsData().then(function (response) {
this.camps = response.campaigns;
console.log(this.camps); // It is showing data
})
console.log(this.camps); // But it is not working :(
};
this.init();
}])
And in my controller
.controller('AdvanceSettingsController', ['$scope', 'CampaignsService', function ($scope, CampaignsService) {
$scope.CampaignsService = CampaignsService;
}
])
I've read this article to learn promises but it is not working here. I can directly achieve it in controller and it's been working fine. But it consider as a bad coding standard to make controller thick. But when I use service and factory I stuck. My question is why I am not getting ajax data to use in my whole service ? I need to use CampaignsService.camps in my view template as well as in my whole rest script but every time I get undefined. What is happening here? I've asked the same question before but couldn't get any success. Some one please help me to understand about promises and why I am getting this type of error if I'm working same ? This type of question has already been asked before but it was working in controller. May be I am stuck because I'm using it in a service.
A big thanks in advance.
This is not a bug or some tricky functionality. Just like in any other AJAX implementation, you can only access the response data in AngularJS's $http success method. That's because of the asynchronous nature of Asynchronous JavaScript And XML.
And what you have is working.
.controller('AdvanceSettingsController', ['$scope', 'AjaxRequests', function ($scope, AjaxRequests) {
$scope.camps = [];
AjaxRequests.getCampaignsData().then(function(data) {
$scope.camps = data;
});
}
])
And then bind camps:
<div ng-repeat="camp in camps>{{camp.name}}</div>
What's bad in your implementation is that instead of grouping related stuff in services you are writing a big AjaxRequests service for everything. You should have a CampaignsService that has a getData method and inject that in your controller.
Why is this working? Because $http does a $scope.$apply for you, which triggers a digest cycle after the data is loaded (then) and updates the HTML. So before the then callback that ng-repeat is run with [] and after it it's again run but with data from the response because you are setting $scope.camps = data;.
The reason <div ng-repeat="camp in CampaignsService.camps>{{camp.name}}</div> does not work is because of function variable scoping.
The this reference inside of your then callback is not the same as the this reference outside of it.
This will work and uses the common var self = this trick:
var self = this;
this.camps = [];
this.init = function () {
AjaxRequests.getCampaignsData().then(function (response) {
// here "this" is not the one from outside.
// "self" on the other hand is!
self.camps = response.campaigns;
});
};

Why is angular $apply required here?

Please consider the following angularjs code for a controller:
(function (app) {
var controller = function ($scope, $state, datacontext) {
$scope.$parent.manageTitle = "Account Management";
$scope.accounts = [];
var init = function () {
getRecords();
};
var getRecords = function () {
return datacontext.getAccounts().then(function (data) {
$scope.$apply(function () {
$scope.accounts = data;
});
});
};
init();
};
app.controller("accountsCtrl", ["$scope", "$state", "datacontext", controller]);
})(angular.module("app"));
Removing the $scope.$apply wrapper and leaving just the "$scope.accounts = data" in the getRecords method breaks the code. The data is retrieved but the ng-repeat directive in the html is not automatically updated. I'm trying to get my arms around the entire $apply/$digest model, but it sure seems to be that the $apply should NOT be required in this case.
Am I doing something wrong?
Thanks.
<------------------------------------------ EDIT ---------------------------------------->
Ok, thanks for the responses. Here is the datacontext. It uses Breeze. I still can't figure out what the problem is - - I just don't see why $apply is required in the code, above.
(function (app) {
var datacontext = function () {
'use strict';
breeze.config.initializeAdapterInstance('modelLibrary', 'backingStore', true);
breeze.config.initializeAdapterInstance("ajax", "angular", true);
breeze.NamingConvention.camelCase.setAsDefault();
var service;
var manager = new breeze.EntityManager('api/ProximityApi');
var entityQuery = breeze.EntityQuery;
var queryFailed = function (error) {
};
var querySuccess = function (data) {
return data.results;
};
var getAccounts = function () {
var orderBy = 'accountName';
return entityQuery.from('Accounts')
.select('id, accountName')
.orderBy(orderBy)
.using(manager)
.execute()
.then(querySuccess, queryFailed);
};
service = {
getAccounts: getAccounts
};
return service;
};
app.factory('datacontext', [datacontext]);
})(angular.module('app'));
Thanks again!
Thanks for your answers. Jared - you're right on the money. By default, Breeze does not use angular $q promises, but uses third-party Q.js promises instead. Therefore, I needed $apply to synchronize the VM to the view. Recently however, the Breeze folks created angular.breeze.js, which allows the Breeze code to use angular promises, instead. By including the angular.breeze module in the application, all Breeze code will use native angular promises and $http instead.
This solved my problem and I could remove the $apply call.
See: http://www.breezejs.com/documentation/breeze-angular-service
The reason that you need to use the $apply function is the result of using Breeze to to return the data. the $apply function is used to get angular to run a digest on all the internal watches and update the scope accordingly. This is not needed when all changes occur in the angular scope as it does this digest automatically. In your code, because you are using Breeze the changes are taking place outside the angular scope, thus you will need to get angular to manually run the digest, and this is true for anything that takes place out side of angular (jQuery, other frameworks ect...). It is true that Breeze is using promises to update the data, however Angular does not know how to handle the changes after the promise returns because it is out side the scope. If you were using an angular service with promises then the view would be updated automatically. If your code is working correctly as is then it would be the correct way to use $apply in this way.
The only thing I might suggest is to change the way you are calling the apply to make sure that it will only run if another digest is not currently in progress as this can cause digest errors. I suggest you call the function as such:
if(!$scope.$$phase){$scope.$apply(function () {
$scope.accounts = data;
});
Or the other option would be to write a custom wrapper around the $apply function like this SafeApply

Resources