I am quite beginner level with both JS and Angular, and I am trying to return data from an API and store it in $scope.
Once I've stored it, I want to loop over each item and output it into the page, pretty basic stuff.
Problem I am having is the API and data is there, but it seems to be returning after the loop is running, is there any way of making the loop wait?
Heres the code;
Service (Hit the endpoint and retrieve the data)
'use strict';
function RecruiterDashJobs($resource, API_URL) {
var dashJobs = {};
dashJobs.getJobs = function(uuid) {
return $resource(API_URL + 'recruiters/' + uuid + '/jobs').get();
}
return dashJobs;
}
angular
.module('app')
.service('RecruiterDashJobs', RecruiterDashJobs);
Controller (Call the service and store the data)
$scope.currentRecruiter = User.getUser();
$scope.getJobs = function(uuid) {
var data = RecruiterDashJobs.getJobs(uuid);
data.$promise.then(
function(res) {
return res.jobs
},
function(err) {
return err;
}
)
};
$scope.recruiterJobs = $scope.getJobs($scope.currentRecruiter.uuid);
View (the Ng-repeat)
<div class="panel border-bottom pad-s-2x pad-e-1x" ng-repeat="job in recruiterJobs">
<div class="panel__body">
<aside class="valign">
<a class="icon--edit color--echo mar-r-2x" ui-sref="jobs/edit/{{job.uuid}"></a>
</aside>
<div class="valign">
<p>{{job.title}}</p>
<p class="color--charlie">Closing Date: {{job.closing_date}}</p>
</div>
</div>
</div>
EDIT: the "magical" approach below no longer works as of Angular 1.2
In your getJobs method, the return statements are inside child functions. You aren't returning the data from getJobs, you're returning it from the function you passed to then.
Two ways to fix it:
The magical Angular way for Angular less than 1.2
Angular views will work with promises for you, so you can just change your getJobs method to this:
$scope.getJobs = function(uuid) {
var data = RecruiterDashJobs.getJobs(uuid);
return data.$promise.then(
function(res) {
return res.jobs
},
function(err) {
return err;
}
)
};
Added return data.$promise...
If you want this to still work in Angular 1.2, you need to call $parseProvider.unwrapPromises(true) somewhere in your code, typically on your main modules config block.
Less magical way or for Angular 1.2 and above
If you want to better understand what is going on, then you can do it this way
$scope.getJobs = function(uuid) {
var data = RecruiterDashJobs.getJobs(uuid);
data.$promise.then(
function(res) {
$scope.recruiterJobs = res.jobs
},
function(err) {
console.log(err);
}
)
};
A $resource call is asynchronous, but $resource immediately returns an empty object that you can embed in your page and will be later populated by response contents. If all goes well, angular should spot the change (because it comes from a $resource process that angular monitors) and update your view accordingly.
So, the behaviour you observe is normal : the very premise of a $promise is that it will be done at a later stage and the process should proceed anyway.
Solutions :
Simply try :
$scope.getJobs = function(uuid) {
var data = RecruiterDashJobs.getJobs(uuid);
return data;
};
If you don't need to post-process data, this should be all you need (except that you might need to call recruiterJobs.jobs in your view, if your response does indeed return an object containing a jobs array, and not the array itself). The page will display, with an initial empty div, then update when data are retrieved and ng-repeat discovers new data to add to the page.
If you do need some post-processing, you can still use your callback :
$scope.getJobs = function(uuid) {
var data = RecruiterDashJobs.getJobs(uuid);
data.$promise.then(
function(res) {
//do something
},
function(err) {
return err;
}
);
return data;
};
If you really need to wait for your data (e.g. because there are some downstream processes that you need them for that can't be postponed), you can use the promise to do so :
$scope.getJobs = function(uuid) {
$scope.preparing = true;
var data = RecruiterDashJobs.getJobs(uuid);
data.$promise.then(function(res) {
$scope.preparing = false;
return data;
});
};
This way, the function will not return until the promise is resolved. I added an optional $scope.preparing flag that you can use in your page to inform the user that something is loading.
Related
So been struggling a bit with getting promises to work properly, but after a lot of work, think I have gotten it. So now comes the question, can you create a self fulfilling promise, if you don't want to wait for the real thing...
Short pseudo version of what I want to do
var promise;
if (!factory.isDataLoaded()){
//The data is not loaded
var promise = factory.init();
} else {
//Data is all loaded
var promise = getSelfFullfilingPromise
}
//Some other code
promise.then(function({
//Do some stuff with the data from factory. which we know is loaded
})
Consider this option (taken straight from my app). I have a factory that loads up a couple of tables, especially one, it translates ids from one table to arrays of names, status and so forth from another table. Then the code does all kinds of wonderful things with it.
OK, it does some massaging and makes pretty diagrams. Problem is that if the initiation of the factory (i.e. do an API call, get some data, store it in a variable) haven't finished, half my page does not render, my boss gets angry, I get fired, and I'll have to dumpster dive behind McDonald (OK, not quite that bad).
So moved my init api call to a promise, sweet.
Then created a helper function that returns status (it checks if there is data in a variable and returns true or false). And I have the request it self (idGetSkill).
I then also have a directive which is called about 20 times, so I don't really want to call my init 20 times in order to ensure that there is data. I only whant to call it if the data is empty, or of skillLoaded returns false.
But if I use .then as a callback, I need a promise that resolves for it to run. So was thinking.
An example of where it could be used:
The Factory
.factory('skillFactory', function($http) {
var skillFactory = [];
var skills = [];
var searchId = [];
var mySkillId = [];
skillFactory.init= function() {
console.log("Got called")
return $http.get('/api/skillList')
.then(function(data){
skills=data['data']
console.log('Skill test ' + skills[0].alias );
})
}
skillFactory.skillLoaded=function(){
if(skills.length < 1) {
console.log("Warning, no data");
return false;
} else {
return true;
}
}
skillFactory.idGetSkill = function(data) {
if (skills.length < 1){
console.log("Warning, no data");
} else {
for (var id in skills) {
if (data == skills[id]._id) {
return skills[id];
}
}
}
}
}
And an app calling it
.controller("PromiseLoad", function ($scope, $http, $window, skillFactory ) {
var promise;
if( ! skillFactory.skillLoaded() ){
promise = skillFactory.init();
} else {
promise = skillFactory.init();
}
var skill = '55c8a069cca746f65c9836a1'
console.log("Will ask for skill " + skill)
promise.then( function() {
console.log("Im done waiting!")
$scope.answer = skillFactory.idGetSkill(skill);
console.log ("And got " +$scope.answer.alias);
})
});
(OK, the above example does not really need it, but its easier to provide this example rather than a directive as that needs a lot more things to work. Like data and stuff:) )
$q.when(data) returns a resolved promise.
$q.reject(data) returns a rejected promise.
I use AngularJS and ngView in my project.
ngView works perfectly with ngRoute, but when I come back to the previus link, ngView reload again data (with Ajax).
Is it possible to "keep in memory" previus data, so that should not always recharge all?
You should store your data in localStorage (or cookieStorage if localStorage is not available), and manage your own cache policy. Then check in your controller if the data is Available and not expired before showing it up. I always use a service of my own that goes like:
(function () {
var __service = function () {
var __set = function (key, item) { localStorage.setItem(key, JSON.stringify(item)); };
var __get = function (key) { return (localStorage.getItem(key) ? JSON.parse(localStorage.getItem(key)) : null); };
var __clear = function (key) { localStorage.removeItem(key); };
var __clearAll = function () { localStorage.clear(); };
return {
Set: __set,
Get: __get,
Clear: __clear,
ClearAll: __clearAll
};
};
angular.module('myApp').service('StorageService', __service);
}());
Then in my controllers I put that service to work in order to cache information that I do not want to retrieve everytime I get there.
PD: I know i must specialize that service to check if localStorage is available.
I have inherited an angular app and now need to make a change.
As part of this change, some data needs to be set in one controller and then used from another. So I created a service and had one controller write data into it and one controller read data out of it.
angular.module('appRoot.controllers')
.controller('pageController', function (myApiService, myService) {
// load data from API call
var data = myApiService.getData();
// Write data into service
myService.addData(data);
})
.controller('pageSubController', function (myService) {
// Read data from service
var data = myService.getData();
// Do something with data....
})
However, when I go to use data in pageSubController it is always undefined.
How can I make sure that pageController executes before pageSubController? Or is that even the right question to ask?
EDIT
My service code:
angular.module('appRoot.factories')
.factory('myService', function () {
var data = [];
var addData = function (d) {
data = d;
};
var getData = function () {
return data;
};
return {
addData: addData,
getData: getData
};
})
If you want your controller to wait untill you get a response from the other controller. You can try using $broadcast option in angularjs.
In the pagecontroller, you have to broadcast your message "dataAdded" and in the pagesubcontroller you have to wait for the message using $scope.$on and then process "getData" function.
You can try something like this :
angular.module('appRoot.controllers')
.controller('pageController', function (myApiService, myService,$rootScope) {
// load data from API call
var data = myApiService.getData();
// Write data into service
myService.addData(data);
$rootScope.$broadcast('dataAdded', data);
})
.controller('pageSubController', function (myService,$rootScope) {
// Read data from service
$scope.$on('dataAdded', function(event, data) {
var data = myService.getData();
}
// Do something with data....
})
I would change your service to return a promise for the data. When asked, if the data has not been set, just return the promise. Later when the other controller sets the data, resolve the previous promises with the data. I've used this pattern to handle caching API results in a way such that the controllers don't know or care whether I fetched data from the API or just returned cached data. Something similar to this, although you may need to keep an array of pending promises that need to be resolved when the data does actually get set.
function MyService($http, $q, $timeout) {
var factory = {};
factory.get = function getItem(itemId) {
if (!itemId) {
throw new Error('itemId is required for MyService.get');
}
var deferred = $q.defer();
if (factory.item && factory.item._id === itemId) {
$timeout(function () {
deferred.resolve(factory.item);
}, 0);
} else {
$http.get('/api/items/' + itemId).then(function (resp) {
factory.item = resp.data;
deferred.resolve(factory.item);
});
}
return deferred.promise;
};
return factory;
}
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 === [...];
});
How do I update/refresh my $scope.list when a new record is added to the db/collection - storage.set() method - please see comment in the code.
Please see code below.
angular.module("app", [])
.factory('Storage', function() {
var storage = {};
storage.get = function() {
return GetStuffHere();
}
storage.set = function(obj) {
return SetStuffHere(obj);
}
return storage;
})
.controller("MainCtrl", function($scope, Storage) {
$scope.addStuff = function(){
var obj = {
"key1" : "data1",
"key2" : "data2"
};
Storage.set(obj);
// update $scope.list here, after adding new record
}
$scope.list = Storage.get();
});
Here's an approach that stores the received data in the service as an array. It uses promises within the service to either send the previously stored array (if it exists) or makes an HTTP request and stores the response. Using promise of $http, it returns the newly stored array.
This now allows sharing of the stored array across other controllers or directives. When adding, editing, or deleting, it is now done on the stored array in the service.
app.controller('MainCtrl',function($scope, Storage){
Storage.get(function(data){
$scope.items=data
});
$scope.addItem=function(){
Storage.set({name: 'Sue'});
}
})
app.factory('Storage', function($q,$http) {
var storage = {};
storage.get = function(callback) {
/* see if already cached */
if( ! storage.storedData){
/* if not, get data from sever*/
return $http.get('data.json').then(function(res){
/* create the array in Storage that will be shared across app*/
storage.storedData=res.data;
/* return local array*/
return storage.storedData
}).then(callback)
}else{
/* is in cache so return the cached version*/
var def= $q.defer();
def.done(callback);
defer.resolve(storage.storedData);
return def.promise;
}
}
storage.set = function(obj) {
/* do ajax update and on success*/
storage.storedData.push(obj);
}
return storage;
})
DEMO
It's not 100% clear what you want to do, but assuming the storage is only going to update when the user updates it (i.e. there's no chance that two users in different locations are going to be changing the same stuff), then your approach should be to either:
Return a promise containing the newly stored object from the storage service after it's completed, and use .then(function() {...}) to set the $scope.list once it's complete.
You would want to take this approach if the storage service somehow mutates the information in a way that needs to be reflected in the front-end (for example an id used to handle future interaction gets added to the object). Note that $http calls return a promise by default so this isn't much extra code if you're using a web service for storage.
Just add the object to the list on the line after you call it with $scope.list.push(obj)
If you have something that changes on the server side without input from that particular client, then I would look into using a websocket (maybe use socket.io) to keep it up to date.
Solution below will work. However, I am not sure if it is best practice to put this in a function and call when needed (within MainCtrl):
i.e:
On first load
and then after new item added
.controller("MainCtrl", function($scope, Storage) {
$scope.addStuff = function(){
var obj = {
"key1" : "data1",
"key2" : "data2"
};
Storage.set(obj);
// rebuild $scope.list after new record added
$scope.readList();
}
// function to bind data from factory to a $scope.item
$scope.readList = function(){
$scope.list = Storage.get();
}
// on first load
$scope.readList();
});
You have to use
$scope.list = Storage.get;
and in template you can then use i.e.
<ul>
<li ng-repeat="item in list()">{{whateverYouWant}}</li>
</ul>
With this approach you will always have the current state of Storage.get() on the scope
couldn't
return SetStuffHere(obj)
just return the updated list as well? and assign that:
$scope.list = Storage.set(obj);
If this is an API endpoint that returns the single inserted item you could push() it to the $scope.list object.
but maybe I'm missing something you are trying to do...
Updating your backend/Factory stuff is a basic Angular binding done by calling a set/post service. But if you want to automatically refresh your controller variable ($scope.list) based on changes occuring in your factory then you need to create a pooler like function and do something like :
.run(function(Check) {});
.factory('Storage', function() {
var storage = {};
var Check = function(){
storage = GetStuffHere();
$timeout(Check, 2000);
}
// set...
Check();
return storage;
})
.controller("MainCtrl", function($scope, Storage) {
$scope.list = Storage.storage;