I am trying to call a service in angular.js through a controller on load and return a promise. I then expect the promise to be fulfilled and for the DOM to be updated. This is not what happens. To be clear, I am not getting an error. The code is as follows.
app.controller('TutorialController', function ($scope, tutorialService) {
init();
function init() {
$scope.tutorials = tutorialService.getTutorials();
}
});
<div data-ng-repeat="tutorial in tutorials | orderBy:'title'">
<div>{{tutorial.tutorialId}}+' - '+{{tutorial.title + ' - ' + tutorial.description}}</div>
</div>
var url = "http://localhost:8080/tutorial-service/tutorials";
app.service('tutorialService', function ($http, $q) {
this.getTutorials = function () {
var list;
var deffered = $q.defer();
$http({
url:url,
method:'GET'
})
.then(function(data){
list = data.data;
deffered.resolve(list);
console.log(list[0]);
console.log(list[1]);
console.log(list[2]);
});
return deffered.promise;
};
});
Inside of the ".then()" function in the service, I log the results and I am getting what I expected there, it just never updates the DOM. Any and all help would be appreciated.
getTutorials returns promise by itself. So you have to do then() again.
tutorialService.getTutorials().then(function(data){
$scope.tutorials = data;
});
Before that, $http returns a promise with success() and error().
Although you can also use then as well
Since the returned value of calling the $http function is a promise,
you can also use the then method to register callbacks, and these
callbacks will receive a single argument – an object representing the
response.
So you are correct with that.
What is your data coming from the http call look like? Your code works - I created a version of it here http://jsfiddle.net/Cq5sm/ using $timeout.
So if your list looks like:
[{ tutorialId: '1',
title : 'the title',
description: 'the description'
}]
it should work
In newer Angular versions (I think v 1.2 RC3+) you have to configure angular to get the unwrap feature working (DEMO):
var app = angular.module('myapp', []).config(function ($parseProvider) {
$parseProvider.unwrapPromises(true);
});
This allows you to directly assign the promise to the ng-repeat collection.
$scope.tutorials = tutorialService.getTutorials();
Beside that I personally prefer to do the wrapping manually:
tutorialService.getTutorials().then(function(tutorials){
$scope.tutorials = tutorials;
});
I don't know the exact reason why they removed that feature from the default config but it looks like the angular developers prefer the second option too.
Related
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
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;
});
};
How can I make an Angular service code "look synchronous"?
My questions arose when I cleaned my controller and put the business logic code into a service instead. So far so good. Now I would like to "wait" in the service function until all asynchronous calls have returned and then return. How can I do that?
To illustrate my problem, suppose you have a controller code which just:
requests some data from the backend
does some processing with the data and
hands the data over to the scope
Like that:
DataController before refactoring:
$scope.submitForm = function() {
RestBackend.query('something').then(function(data) {
// do some additional things ...
...
$scope.data = data;
});
};
Pretty straightforward. Fetch data and fill scope.
After refactoring into controller + service, I ended up with:
DataController refactored:
$scope.submitForm = function() {
DataService.getData().then(function(data) {
$scope.data = data;
});
};
DataService refactored:
this.query = function() {
var dataDefer = $q.defer();
RestBackend.query('something').then(function(data) {
// do some additional things ...
...
dataDefer.resolve(data);
});
return dataDefer.promise;
};
I dislike the fact that I have to work with a promise in the controller also. I like promises but I want to keep the controller agnostic of this "implementation detail" of the service. This is what I would like the controller code to look like:
DataController (as it should be):
$scope.submitForm = function() {
$scope.data = DataService.getData();
};
You get the point? In the controller I don't want to care about promise or not. Just wait for the data to be fetched and then use it. Thus, I am looking for a possibility to implement the service like this:
query the data (asynchronously)
do not return until the data has been fetched
return the fetched data
Now item 2. is not clear to me: How can I "wait until data has been fetched" and only proceed afterwards? The goal is that the service function looks synchronous.
I too think your solution is fine.
Returning a promise is not an implementation detail of the service. It is part of the service's API (the "contract" between the service and the service-consumer).
The controller expects a promise that resolves with the data and handles that as it sees fit.
How that promise is constructed, how the data is fetched etc, these are the implementation details.
You can swap the service at any time with one that does totally different things as long as it fulfills the contract (i.e. returns a promise that resolves with the data onve ready).
That said, if you only use the data in the view (i.e. do not directly manipulate it in the controller right after it is fetched), which seems to be the case, you can use ngResources approach:
Return an empty array and populate it with the data once it is fetched:
$scope.data = DataService.getData();
// DataService refactored:
this.getData = function () {
var data = [];
RestBackend.query('something').then(function(responseData) {
// do some additional things ...
...
angular.forEach(responseData, function (item) {
data.push(item);
});
});
return data;
};
BTW, in your current (fine) setup, you need $q.defer(). You can just use promise-chaining:
this.query = function() {
return RestBackend.query('something').then(function(data) {
// do some additional things ...
...
return data;
});
};
I think what you have is a very good solution. You should not have to wait for promise to be resolved, it defeats the purpose of async javascript. Just ask yourself why do you need to make it run sync?
If you rely in html on promise to be resolve you can do something like this
<div class="alert alert-warning text-center" data-ng-hide="!data.$resolved">
Got data from service.
</div>
As you use ngRoute, I would recommend you to resolve you data in your route config, and the view will be loaded once all your data will be resolved.
$routeProvider
.when('/your-url', {
templateUrl: 'path/to/your/template.html',
controller: 'YourCtrl',
// that's the point !
resolve: {
superAwesomeData: function (DataService) {
return DataService.getData();
}
}
});
Now, superAwesomeData can be injected in your controller and it will contains the data, resolved.
angular.module('youModule')
.controller('YourCtrl', function (superAwesomeData) {
// superAwesomeData === [...];
});
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
I am using some data which is from a RESTful service in multiple pages.
So I am using angular factories for that. So, I required to get the data once from the server, and everytime I am getting the data with that defined service. Just like a global variables. Here is the sample:
var myApp = angular.module('myservices', []);
myApp.factory('myService', function($http) {
$http({method:"GET", url:"/my/url"}).success(function(result){
return result;
});
});
In my controller I am using this service as:
function myFunction($scope, myService) {
$scope.data = myService;
console.log("data.name"+$scope.data.name);
}
Its working fine for me as per my requirements.
But the problem here is, when I reloaded in my webpage the service will gets called again and requests for server. If in between some other function executes which is dependent on the "defined service", It's giving the error like "something" is undefined. So I want to wait in my script till the service is loaded. How can I do that? Is there anyway do that in angularjs?
You should use promises for async operations where you don't know when it will be completed. A promise "represents an operation that hasn't completed yet, but is expected in the future." (https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise)
An example implementation would be like:
myApp.factory('myService', function($http) {
var getData = function() {
// Angular $http() and then() both return promises themselves
return $http({method:"GET", url:"/my/url"}).then(function(result){
// What we return here is the data that will be accessible
// to us after the promise resolves
return result.data;
});
};
return { getData: getData };
});
function myFunction($scope, myService) {
var myDataPromise = myService.getData();
myDataPromise.then(function(result) {
// this is only run after getData() resolves
$scope.data = result;
console.log("data.name"+$scope.data.name);
});
}
Edit: Regarding Sujoys comment that
What do I need to do so that myFuction() call won't return till .then() function finishes execution.
function myFunction($scope, myService) {
var myDataPromise = myService.getData();
myDataPromise.then(function(result) {
$scope.data = result;
console.log("data.name"+$scope.data.name);
});
console.log("This will get printed before data.name inside then. And I don't want that.");
}
Well, let's suppose the call to getData() took 10 seconds to complete. If the function didn't return anything in that time, it would effectively become normal synchronous code and would hang the browser until it completed.
With the promise returning instantly though, the browser is free to continue on with other code in the meantime. Once the promise resolves/fails, the then() call is triggered. So it makes much more sense this way, even if it might make the flow of your code a bit more complex (complexity is a common problem of async/parallel programming in general after all!)
for people new to this you can also use a callback for example:
In your service:
.factory('DataHandler',function ($http){
var GetRandomArtists = function(data, callback){
$http.post(URL, data).success(function (response) {
callback(response);
});
}
})
In your controller:
DataHandler.GetRandomArtists(3, function(response){
$scope.data.random_artists = response;
});
I was having the same problem and none if these worked for me. Here is what did work though...
app.factory('myService', function($http) {
var data = function (value) {
return $http.get(value);
}
return { data: data }
});
and then the function that uses it is...
vm.search = function(value) {
var recieved_data = myService.data(value);
recieved_data.then(
function(fulfillment){
vm.tags = fulfillment.data;
}, function(){
console.log("Server did not send tag data.");
});
};
The service isn't that necessary but I think its a good practise for extensibility. Most of what you will need for one will for any other, especially when using APIs. Anyway I hope this was helpful.
FYI, this is using Angularfire so it may vary a bit for a different service or other use but should solve the same isse $http has. I had this same issue only solution that fit for me the best was to combine all services/factories into a single promise on the scope. On each route/view that needed these services/etc to be loaded I put any functions that require loaded data inside the controller function i.e. myfunct() and the main app.js on run after auth i put
myservice.$loaded().then(function() {$rootScope.myservice = myservice;});
and in the view I just did
ng-if="myservice" ng-init="somevar=myfunct()"
in the first/parent view element/wrapper so the controller can run everything inside
myfunct()
without worrying about async promises/order/queue issues. I hope that helps someone with the same issues I had.