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.
Related
I have an API call that's working great, but I'd like to use it on several controllers so I moved it to it's own service. I'm running into what looks like a classic Scope issue or a misunderstanding of Angular's digest cycle.
'use strict';
myApp.factory('Stuff',['$http', function ($http) {
var Stuff = {};
Stuff.data = {};
Stuff.api = 'http://localhost:8080/api/';
Stuff.getStuff = function() {
var http_stuff_config = {
method: 'GET',
url: Stuff.api + 'stuff/'
};
$http(http_stuff_config).then(function successCallback(response) {
Stuff.data = (response.data);
console.log(Stuff.data); // Returns populated object.
},function errorCallback(response) {
console.log(response.statusText);
});
};
Stuff.getStuff();
console.log(Stuff.data); // Returns empty object.
return Stuff;
}]);
myApp.controller('appController', ['$scope','Stuff',function($scope,Stuff) {
$scope.stuff = Stuff;
console.log($scope.stuff.data); // Returns empty object.
$scope.stuff.getJobs();
console.log($scope.stuff.data); // Returns empty object.
}]);
Here's the big clue. The essential output of above, in order is...
empty object (in service after calling method)
empty object (in controller before calling method)
empty object (in controller after calling method)
populated object (in method execution from service)
populated object (in method execution from controller)
So somewhere between the scope of the getStuff() method and Angular's order of operations, I'm doing something remarkably foolish. Thank you in advance.
You need to add returns on your service, or else the promise will not be returned to the controller. It is not good practice to just store the returns in your services AND NOT return the result to the controller.
This is considered bad practice because, any time you update the data on the service everyone will need to apply $scope.$watch to the service to look for updates. This can be very expensive in large scale apps.
The best Idea is to return the data to the calling controller (if you do not need to cache it, this we can talk about later) and let the controller access it via the promise service.getthing().then(function(result){});
myApp.factory('Stuff',['$http', function ($http) {
var Stuff = {};
Stuff.data = {};
Stuff.api = 'http://localhost:8080/api/';
Stuff.getStuff = function() {
var http_stuff_config = {
method: 'GET',
url: Stuff.api + 'stuff/'
};
return $http(http_stuff_config).then(function successCallback(response) {
return response.data;
console.log(Stuff.data); // Returns populated object.
},function errorCallback(response) {
console.log(response.statusText);
});
};
Stuff.getStuff();
console.log(Stuff.data); // Returns empty object.
return Stuff;
}]);
myApp.controller('appController', ['$scope','Stuff',function($scope,Stuff) {
$scope.stuff = Stuff;
console.log($scope.stuff.data); // Returns empty object.
$scope.stuff.getJobs().then(function(result) {$scope.stuff = result; console.log(result);});
console.log($scope.stuff.data); // Returns empty object.
}]);
I recommend you not to store the result inside the service itself (Stuff.data). Just return your data in the getStuff function and let the appController's scope store the data instead.
remember that $scope.stuff.getJobs() is async
(meaning you can't actually call console.log($scope.stuff.data) on the next line and get the data)
Now if you had a view, with something like <span ng-bind="stuff.data.property"> you could see it work just fine because the view will update by itself when the async function is done. (this is part of angular)
You need to understand that when you run $http, it is making an AJAX request. therefore it will not return an result immediately.
Therefore, if you attempt to use the data coming from $scope.stuff.getJobs(); immediate after invoking this function, you are likely to get nothing.
What you should do is to have your Stuff.getJobs() return a promise, and use promise.then(your own success handler) to correctly handle the returned response.
I have cleaned up your code a little bit. The following is a running sample of your code retrieving data from Yahoo Weather API.
You can play with it on CODEPEN.
html:
<div ng-app="myApp" ng-controller="appController">
<p>{{data}}</p>
</div>
JS:
var myApp = angular.module("myApp", []);
myApp.factory('Stuff',['$http', function ($http) {
var Stuff = {};
Stuff.data = {};
//sample yahoo weather api
Stuff.api = 'https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20weather.forecast%20where%20woeid%20in%20(select%20woeid%20from%20geo.places(1)%20where%20text%3D%22nome%2C%20ak%22)&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys';
Stuff.getData = function() {
var http_stuff_config = {
method: 'GET',
url: Stuff.api + 'stuff/'
};
return $http(http_stuff_config);
};
return Stuff;
}]);
myApp.controller('appController', ['$scope','Stuff',function($scope,Stuff) {
$scope.data = "$http service not ran";
var uncompletedAjaxCall = Stuff.getData();
uncompletedAjaxCall.then(
function(responseData){
$scope.data = responseData;
},
function(errorMsg){}
);
}]);
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;
});
I'm trying to create a controller that gets data from Google app engine and allows me to display it on a page. The problem seems to be that the data (resp) can be accessed locally, but I can't seem to access it outside of the function. I am able to do so if I simply use javascript (...document.getElementById('getListingsResult').innerHTML = result;...), but if I invoke $scope for Angular, I can't access it any longer. Does anyone have any idea of how I can fix it while retaining the same structure to load and call gapi? Heres' my code:
(edit: added $scope.loadData, but problem persists)
phonecatControllers.controller('datastoreTestCtrl', ['$scope',
function($scope) {
$scope.data;
$scope.loadData = function() {
var ROOT = 'https://my_team.appspot.com/_ah/api';
gapi.client.load('listingserviceapi', 'v1', function(){
console.log("reached step 1");
var request = gapi.client.listingserviceapi.getListings();
request.execute(function (resp){
if (!resp.code) {
// console.debug(resp);
console.log('loaded! :)');//returns loaded
resp.items = resp.items || [];
$scope.data = resp.items;
console.log($scope.data); //returns an array of data
}
};
} , ROOT );};
$scope.loadData;
console.log($scope.data); //returns [object, object] which is incorrect
}]);
It should work using promise. Also, there is a missing parenthesis for request.execute function in your code.
Check the below code (untested):
phonecatControllers.controller('datastoreTestCtrl', ['$scope', '$q',
function ($scope, $q) {
$scope.data = null;
$scope.loadData = function () {
var deferred = $q.defer(),
ROOT = 'https://my_team.appspot.com/_ah/api';
gapi.client.load('listingserviceapi', 'v1', function () {
console.log("reached step 1");
var request = gapi.client.listingserviceapi.getListings();
request.execute(function (resp) {
if (!resp.code) {
// console.debug(resp);
console.log('loaded! :)'); //returns loaded
resp.items = resp.items || [];
//$scope.data = resp.items;
//console.log($scope.data); //returns an array of data
deferred.resolve(resp.items);
}
}); //---missing parenthesis here
}, ROOT);
return deferred.promise;
};
$scope.loadData().then(function (data) {
$scope.data = data;
console.log($scope.data); //returns [object, object] which is incorrect
});
}]);
That is because you are doing asynchronous call. When you trying to access $scope.data from outside of your callback your request is not finished yet it is still in process. You have to make sure that your request is done.
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;
})
Im learning AngularJs.
And I find my self enjoying it, But im stuck with this code
my controller
$scope.getQuestionaires = function(){
var formdata = $scope.formdata;
var items = parseInt(formdata.items);
var num_letter = parseInt(formdata.num_letter);
var num_missing_letter = parseInt(formdata.num_missing_letter);
var send_data = {
api_type: 'select',
tb_name: 'tb_spelling',
tb_fields: false,
tb_where: false,
tb_others: "LIMIT "+items
};
return factory.getRecords(send_data);
}
my factory
factory.getRecords = function(data) {
return $http.post('models/model.php', {
params: data
}).then(function(response) {
records = response.data;
return records;
});
};
Situation : When I console.log($scope.getQuestionaires), It returns
function (b,j){var
g=e(),i=function(d){try{g.resolve((b||c)(d))}catch(e){a(e),g.reject(e)}},o=function(b){try{g.resolve((j||
d)(b))}catch(c){a(c),g.reject(c)}};f?f.push([i,o]):h.then(i,o);return
g.promise} controllers.js:307 function (a){function b(a,c){var
d=e();c?d.resolve(a):d.reject(a);return d.promise}function d(e,f){var
j=null;try{j=(a||c)()}catch(g){return b(g,!1)}return
j&&j.then?j.then(function(){return b(e,f)},function(a){return
b(a,!1)}):b(e,f)}return this.then(function(a){return
d(a,!0)},function(a){return d(a,!1)})} controllers.js:307
[Object, Object, Object, Object]
Question : My problem is that i only want the array of objects, how can i do that? I think theres a lot i got to improve about my code...I need help :)
Thx
====================================
Fixed
Thx to Chandermani's answer,
Got it!
My controller
$scope.createTest = function(){
$scope.getQuestionaires();
}
/*question getter*/
$scope.getQuestionaires = function(id,question){
/*send_data here*/
var records = factory.getRecords(send_data);
records.then(function(response){
$scope.questionaires = response.data;
});
}
My factory
factory.getRecords = function(data) {
return $http.post('models/model.php', {
params: data
});
};
The method getQuestionaires returns response for your $http post method which is a promise and not the actual data, since the call is async.
Change the method getRecords to
factory.getRecords = function(data) {
return $http.post('models/model.php', {
params: data
});
};
When you call the method getQuestionaires do something like
$scope.getQuestionaires().then(function(response){
//access response.data here
});
Try to understand the async nature of such request. If you ever call async methods within your own functions you can access the response by either returning the promise or provide a callback as a argument to the function.