I think I need (but I'm not sure) to use a promise in my Angularjs app. This following code is within my controller and it calls a .service (not a .factory if that's relevant?) called processString.
//complete this first
var item = MagicBoxService.processString(str);
//and then do this
$scope.task.items.push({ content_type: item.content_type, provider: item.provider, front: item.front, data: item.data });
$scope.save = true;
The service needs to communicate with a 3rd party API (as well as my own) to get the data. This happens very quickly but the item variable is empty when the next part of the code is executed.
I've tried a $timeout on the API call but this doesn't seem to work so I thought maybe a promise is what I need to use so I've tried the following:
var item = MagicBoxService.processString(str).then(function() {
$scope.task.items.push({ content_type: item.content_type, provider: item.provider, front: item.front, data: item.data });
$scope.save = true;
})
but this gives me undefined is not a function. Any advice/code would be much appreciated.
EDIT
Here is an edited version of my .service.
this.processString = function(str) {
...
oEmbedService.query({url: str}, function(response) {
item.content_type = "image";
item.provider = "Flickr";
item.data = response.content.url;
item.front = $sce.trustAsResourceUrl(item.data);
})
...
return item
};
if your processString() function looks like this:
function(str) {
var promiseManager = $q.defer();
...
oEmbedService.query({url: str}, function(response) {
var item = {};
item.content_type = "image";
item.provider = "Flickr";
item.data = response.content.url;
item.front = $sce.trustAsResourceUrl(item.data);
promiseManager.resolve(item);
})
...
return promiseManager.promise;
};
Then you can call it like this:
MagicBoxService.processString(str).then(function(item) {
$scope.task.items.push({ content_type: item.content_type, provider: item.provider, front: item.front, data: item.data });
$scope.save = true;
})
Related
I have an factory that gets data from my backend:
as.factory("abbdata", function GetAbbData($http,$rootScope,$routeParams,$q) { //$q = promise
var deffered = $q.defer();
var data = [];
var abbdata = {};
abbdata.async = function () {
$http.get($rootScope.appUrl + '/nao/summary/' + $routeParams['id']).success(function(d) {
data = d.abbData;
deffered.resolve();
});
return deffered.promise;
};
abbdata.data = function() {
return data;
};
return abbdata;
});
A call my factory like this in my controller:
abbdata.async().then(function() {
$scope.abbData = abbdata.data(); //Contains data
});
When I do a console.log($scope.abbData) outside my service call, just underneath, the result Is undifined. Why? Should not the $scope.abbData contain the data from my service after I call it?
EDIT:
You need to pass the data that should be returned into the resolve function like this:
deffered.resolve(data);
EDIT:
To get the data in the controller do this:
abbdata.async().then(function(data) {
$scope.abbData = data; //Contains data
});
Why don't you simply return that value from the async call in the first place?
You can chain promises so by attaching a success handler in your factory and returning a value from that you can simplify your code to:
as.factory("abbdata", function GetAbbData($http,$rootScope,$routeParams) {
return {
async: function () {
return $http.get($rootScope.appUrl + '/nao/summary/' + $routeParams['id']).success(function(d) {
return d.data.abbData;
});
}
}
});
And then use it like
abbdata.async().then(function(data) {
$scope.abbData = data; //Contains data
});
if you console.log($scope.abbData) outside the service call it should show undefined, since the call is asynchronous.
abbdata.async().then(function() {
$scope.abbData = abbdata.data(); //Contains data
});
console.log($scope.abbData) // this should show undefined
The console.log($scope.abbData) just after setting the abbData should show the data
abbdata.async().then(function() {
$scope.abbData = abbdata.data(); //Contains data
console.log($scope.abbData) // this should show the data
});
EDIT
you can use abbData from your service call like for example
angular.module('myApp', []).controller('HomeCtrl', function($scope, abbdata){
var updateUI;
$scope.abbData = [];
abbdata.async().then(function() {
$scope.abbData = abbdata.data(); //Contains data
updateUI();
});
updateUI = function(){
//do something with $scope.abbData
}
});
EDIT 2
On response to your query, I would do something like,
angular.module('myApp', [])
.controller('JobsCtrl', function($scope, $jobService) {
$scope.jobs = [];
$jobService.all().then(function(jobs) {
$scope.jobs = jobs;
});
})
.service('$jobService', function ($q, $http) {
return {
all: function () {
var deferred = $q.defer();
$http({
url: 'http://url',
method: "GET"
}).success(function (data) {
deferred.resolve(data);
}).error(function () {
deferred.reject("connection issue");
});
return deferred.promise;
}
}
});
associated view
<body ng-app = "myApp">
<div ng-controller = "JobsCtrl">
<div ng-repeat="job in jobs track by job.id">
<a href="#/tab/jobs/{{job.id}}" class="item item-icon-right">
<h2>{{job.job_name}}</h2>
<p>DUE DATE: {{job.job_due_date}}</p>
</a>
</div>
<div>
</body>
Here the service an all function which returns a promise, i.e. it will notify when data is fetched.
in the controller the service is called and as soon the service call is resolved the $scope.jobs is assigned by the resolved data.
the $scope.jobs is used in the angular view. as soon as the jobs data are resolved, i.e. $scope.jobs is assigned, the view is updated.
hope this helps
I had a quick look, I have 2 ideas:
First theory: your service is returning undefined.
Second theory: you need to run $scope.$apply();
See this fiddler: https://jsfiddle.net/Lgfxtfm2/1/
'use strict';
var GetAbbData = function($q) {
//$q = promise
var deffered = $q.defer();
var data = [];
var abbdata = {};
abbdata.async = function () {
setTimeout(function() {
//1: set dummy data
//data = [200, 201];
//2: do nothing
//
//3: set data as undefined
//data = undefined;
deffered.resolve();
}, 100);
return deffered.promise;
};
abbdata.data = function() {
return data;
};
return abbdata;
};
var abbdata = GetAbbData(Q)
abbdata.async().then(function() {
console.log(abbdata.data()); //Contains data
});
I have stripped away a lot of dependencies and replaced $q with Q just for my own ease.
In the above example, I first attempted to run the code with dummy data, the console output the expected data, then I tried to not assign the data, and I get an empty array. This is why I assume that if you are seeing 'undefined' you must be explicitly setting the value to 'undefined'.
That aside, I also noticed that you were testing the result by reading directly from $scope. I know that when not inside the angular scope, doing operations on the $scope object does not necessarily happen in a timely manner, and typing $scope.$apply() usually fixes this. Usually, when using $http, angular keeps you in the appropriate scope, but you are creating your own promise using $q so this could be another potential issue.
Finally, the other two answers have pointed out that you are not using promises in the standard way. Although your code works fine, it is not normal to set your data directly onto your service and retrieve it from there. You can keep your service stateless by simply resolving your promise with the data that you want to process in the then method as shown by the answers by Anzeo and Markus.
I hope I was able to find the solution, good luck.
Dipun
as.factory("abbdata", function GetAbbData($http,$rootScope,$routeParams,$q) { //$q = promise
var deffered = $q.defer();
var data = [];
var abbdata = {};
abbdata.async = function () {
$http.get($rootScope.appUrl + '/nao/summary/' + $routeParams['id']).success(function(d) {
data = d.abbData;
deffered.resolve(data);
});
return deffered.promise;
};
abbdata.data = function() {
return data;
};
return abbdata;
});
So i have the following object:
$scope.post = {user_id: $sessionStorage.user.user.id};
with this i have the following:
<textarea class="form-control" ng-model="post.text" style="overflow:scroll;height:150px;max-height:150px"></textarea>
On submit i want to do the following action:
$scope.addPost = function () {
$http.post(api.getUrl('post', null),
{
post: $scope.post
}).success(function (response) {
$scope.post.id = response;
$scope.posts.push($scope.post);
});
$scope.post = {};
}
However when i clear $scope.post because of the databinding the post is empty. So my question is how can i avoid this?
You can make a copy using angular.copy() which will return a new object with no references to the original.
This also removes any hashkeys that angular scope has added to the object which can be problematic when server sees unrecognized keys
$scope.addPost = function () {
var postData = angular.copy($scope.post);
$http.post(api.getUrl('post', null),
{
post: postData
}).success(function (response) {
$scope.post.id = response;
$scope.posts.push(postData);
$scope.post = {}; //wait for success to clear
});
}
As already noted should wait for success to clear the live version
It's because of asynchronous nature of Ajax call - your $scope.post = {}; is executed earlier than success / error callbacks.
You should do clearing $scope.post inside the callback, not outside.
I'm trying to call an API that takes parameters start and count, like this:
function handleSuccess() {
if (!!response.data) {
return (response.data);
} else {
return q.reject(response.data);
}
}
function handleError() {
// do some handling
}
function getData(url, sortBy) {
var count = 10;
var start = 1;
var request = http({
cache: true,
method: "GET",
url: url,
params: {
sortBy: sortBy,
sortOrder: "ASC",
count: count, // e.g. 10
start: start // e.g. 1
}
});
return (request.then(handleSuccess, handleError));
}
The JSON response from the API could contain a "next" link that would give me the URL to call to get the next set of data if there exists more...this is how the pagination works.
What's the best way to do this and concatenate all the data returned into one JSON response?
Assuming some part of the data response is an array, then simply use normal array concat() to combine it with previous pages of data in your handleSuccess() callback.
I've found that a service-oriented way is most useful when trying to get paged data from the same endpoint as it is easy to share services and objects between controllers and directives.
First, I would set up the service layer of your application so that all objects being requested have common, generic methods like so (I highly recommend you use ngResource or RESTAngular or something like that):
angular.module('myModule')
.factory('ApiObject', function($http, $q) {
ApiObject = function ApiObject(attributes) {
angular.extend(this, attributes);
};
ApiObject.query = function(url, parameters) {
var deferred = $q.defer();
$http.get(url, {params: parameters}).then(function(data) {
var results = [];
angular.forEach(data, function(apiObject) {
results.push(new ApiObject(apiObject));
});
deferred.resolve(results);
}, function(error) {
// Do error stuff
deferred.reject(error);
});
return deferred.promise;
};
return ApiObject;
});
Then set up a service to manage your paging data that accepts your generic services as well as parameters and configuration options. Also allow for events to be triggered within the service (see trigger and on methods) so that you know when new results are fetched. I've also written in a way for the results to be automatically concatenated onto the current result set:
angular.module('myModule')
.factory('SearchService', function() {
SearchService = function SearchService(service, params, config) {
this.searchParams = params || {};
this.config = config || {};
this.service = service;
this.results = [];
this.listeners = {};
};
SearchService.prototype.fetch = function(params) {
var _this = this;
this.service.query().then(function(results) {
if(_this.config.concatResults) {
_this.results = _this.results.concat(results);
// You probably should make sure results are unique at this point as that is a common problem with paging a changing API
} else {
_this.results = results;
}
_this.trigger('searchSuccess', _this.results);
});
};
SearchService.prototype.on = function(event, listener) {
(this.listeners[event] = (this.listeners[event] || [])).push(listener);
};
SearchService.prototype.trigger = function(event, payload) {
angular.forEach(this.listeners[event], function(listener) {
listener(payload);
});
};
SearchService.prototype.isLastPage = function() {
//logic here to determine last page
};
SearchService.prototype.nextPage = function() {
if(this.isLastPage()) {
return;
}
if(this.searchParams.page) {
this.searchParams.page++;
} else {
this.searchParams.page = 2;
}
this.fetch();
};
// Write more methods for previousPage, lastPage, firstPage, goToPage... etc.
return SearchService;
});
Then in your controller, you're going to want to instantiate the search service with some default parameters and configuration and then fetch the first page:
angular.module('myModule')
.controller('MyCtrl', function($scope, ApiObject, SearchService) {
$scope.searchService = new SearchService(ApiObject, {page: 1}, {concatResults: true});
$scope.searchService.on('searchSuccess', function(results) {
// Do something with results if you wish, but they'll already be stored in $scope.searchService
});
// Get the first page of data
$scope.searchService.fetch();
});
This is obviously a rough cut with a lot of room for improvement, but I hope this will be a good jumping off point to get you pointed in some sort of angular-style direction. In my experience, this is the best way to abstract out paging logic from the data/request layer in your services.
I m trying to use a controller callback function inside my service, when it successes an $http post request. Here's my code, the exact description is a bit below.
my controller :
function UserAccountCtrl (UserService, $rootScope, listUsers) {
$rootScope.title = 'Comptes utilisateurs';
this.users = listUsers.data;
this.isShown = false;
this.isModification = false;
this.deleteEntry = function(entry){
this.users.splice(this.users.indexOf(entry), 1);
UserService.delete(entry);
};
this.show = function(){
this.isShown = true;
};
this.hide = function(){
this.isShown = false;
};
this.save = function(){
var success = function(data){
this.users.push(data);
};
var err = function(data){
alert(data);
};
UserService.post(this.user, success, err);
this.hide();
};
}
My service function :
UserService.post = function (data,succ,err) {
$http({
url: __ADRS_SRV__ + "user",
method: "POST",
data:data,
isArray: true
}).success(function(data){
succ(data);
}).error(function(error){
err(error);
});
}
The functionnement is simple : when I post a new user, the WebService inserts it in mongo, and returns the fully new object generated. I can get the new object from console.log or with an alert, and it works fine.
But I cant push the new item in my array. I have the error :
Uncaught TypeError: undefined is not a function
At the exact line where I need to push the new item.
So, does anybody have an idea ?
Thanks for advance
this in this.users.push(data); is not the same this as outside of the function and therefore does not have a users array to push the new data to. (See MDN this for more info on this in javascript)
I would actually not use this at all and attach everything to the $scope object as required. This would get around your issue as $scope will be the same no matter what the context.
function UserAccountCtrl ($scope, UserService, $rootScope, listUsers) {
$scope.users = listUsers.data;
$scope.save = function(){
var success = function(data){
$scope.users.push(data);
};
var err = function(data){
alert(data);
};
UserService.post($scope.user, success, err);
$scope.hide();
};
// do the same with other functions
}
See 'this' vs $scope in AngularJS controllers for more details.
Can you try if this works?
this.save = function(){
var self = this;
var success = function(data){
self.users.push(data);
};
var err = function(data){
alert(data);
};
UserService.post(this.user, success, err);
this.hide();
};
I'm new to AngularJS and am still trying to wrap my head around using services to pull data into my application.
I am looking for a way to cache the result of a $http.get() which will be a JSON array. In this case, it is a static list of events:
[{ id: 1, name: "First Event"}, { id: 2, name: "Second Event"},...]
I have a service that I am trying to use to cache these results:
appServices.service("eventListService", function($http) {
var eventListCache;
this.get = function (ignoreCache) {
if (ignoreCache || !eventListCache) {
eventListCache = $http.get("/events.json", {cache: true});
}
return eventListCache;
}
});
Now from what I can understand I am returning a "promise" from the $http.get function, which in my controller I add in a success callback:
appControllers.controller("EventListCtrl", ["$scope", "eventListService",
function ($scope, eventListService) {
eventListService.get().success(function (data) { $scope.events = data; });
}
]);
This is working fine for me. What I'd like to do is add an event to the eventListService to pull out a specific event object from eventListCache.
appServices.service("eventListService", function($http) {
var eventListCache;
this.get = function (ignoreCache) { ... }
//added
this.getEvent = function (id) {
//TODO: add some sort of call to this.get() in order to make sure the
//eventListCache is there... stumped
}
});
I do not know if this is the best way to approach caching or if this is a stupid thing to do, but I am trying to get a single object from an array that may or may not be cached. OR maybe I'm supposed to call the original event and pull the object out of the resulting array in the controller.
You're on the right track. Services in Angularjs are singeltons, so using it to cache your $http request is fine. If you want to expose several functions in your service I would do something like this. I used the $q promise/deferred service implementation in Angularjs to handle the asynchronus http request.
appServices.service("eventListService", function($http, $q) {
var eventListCache;
var get = function (callback) {
$http({method: "GET", url: "/events.json"}).
success(function(data, status) {
eventListCache = data;
return callback(eventListCache);
}).
}
}
return {
getEventList : function(callback) {
if(eventListCache.length > 0) {
return callback(eventListCache);
} else {
var deferred = $q.defer();
get(function(data) {
deferred.resolve(data);
}
deferred.promise.then(function(res) {
return callback(res);
});
}
},
getSpecificEvent: function(id, callback) {
// Same as in getEventList(), but with a filter or sorting of the array
// ...
// return callback(....);
}
}
});
Now, in your controller, all you have to do is this;
appControllers.controller("EventListCtrl", ["$scope", "eventListService",
function ($scope, eventListService) {
// First time your controller runs, it will send http-request, second time it
// will use the cached variable
eventListService.getEventList(function(eventlist) {
$scope.myEventList = eventlist;
});
eventListService.getSpecificEvent($scope.someEventID, function(event) {
// This one is cached, and fetched from local variable in service
$scope.mySpecificEvent = event;
});
}
]);
You are on the right track. Here's a little help:
appServices.service("eventListService", function($http, $q) {
var eventListCache = [];
function getList(forceReload) {
var defObj = $q.defer(), listHolder;
if (eventListCache.length || forceReload) {
listHolder= $http.get("/events.json", {cache: true});
listHolder.then(function(data){
eventListCache = data;
defObj.resolve(eventListCache);
});
} else {
defObj.resolve(eventListCache);
}
return defObj.promise;
}
function getDetails(eventId){
var defObj = $q.defer();
if(eventId === undefined){
throw new Error('Event Id is Required.');
}
if(eventListCache.length === 0){
defObj.reject('No Events Loaded.');
} else {
defObj.resolve(eventListCache[eventId]);
}
return defObj.promise;
}
return {
eventList:getList,
eventDetails:getDetails
};
});
Then, in your controller, you handle it like this:
appControllers.controller("EventListCtrl", ["$scope", "eventListService",
function ($scope, eventListService) {
var eventList = eventListService.getList();
eventList.then(function(data){
$scope.events = data;
});
$scope.getEventsList = function(reloadList){
eventList = eventListService.getList(reloadList);
eventList.then(function(data){
$scope.events = data;
});
};
$scope.getEventDetails = function(eventID){
var detailsPromise = eventListService.getDetails(eventID);
detailsPromise.then(function(data){
$scope.eventDetails = data;
}, function(reason){
window.alert(reason);
});
}
}
]);
This way, your events are loaded when the controller first loads, and then you have the option to request a new list by simply passing in a boolean. Getting event details is also handled by an internal promise to give you some error handling without throwing a disruptive error.