How to get data in Angular directive though Ajax calls? - javascript

Please forgive me if my Title doesn't exactly match with the issue.
I have a custom Angular directive to generate kendo grids dynamically.
I am making an Ajax call to get the configuration from server.
Now issue is, directive first gets loaded and then the ajax call gets completed because of which it is throwing error in my directive.
Please let me know if there is any workaround for this.
My directive:
gridApp.directive('grid', function () {
return {
restrict: "EA",
scope: true,
template: '<div kendo-grid="mainGrid" options="gridOptions"></div>',
controller: function ($scope, $element, $attrs, gridService) {
var gridConfig = gridService.getGridConfig(); //this is undefined. However, when I check in console it gets loaded once the directive is executed.
//removed for breveity
}
};
});
My Service:
angularApp.factory('gridService', gridService);
function gridService($http, $q) {
var getGridConfig = function (gridId) {
var deferred = $q.defer();
$http.get('/Base/GetGridConfiguration?GridId=' + gridId)
.then(function (response) {
deferred.resolve(response.data);
});
return deferred.promise;
}
return {
getGridConfig: getGridConfig
};
}

promises are async. you need to use .then
gridService.getGridConfig()
.then(function(data) {
////
// code dependent on server response
////
});
RFTD link

Chnage your service to return deferred object rather than data.
return $http.get('/Base/GetGridConfiguration?GridId=' + gridId)
resolve the promise inside the directive.
var gridConfig, _self = this;
gridService.getGridConfig().then((response) => {
_self.gridConfig = response.data;
});

Related

How to return HTTP response through subsequent service call

I am facing problem in returning data from Web API to Angular Form via series of Common Function. I am giving the code in its simplest form to understand the problem:-
HTML Form:-
<label>Request Number</label>
<input type="text" id="txtRequestNum" ng-model="md_reqnumber" />
<label>Employee Number</label>
<input type="text" id="txtEmployeeId" ng-model="md_number" ng-disabled="true" />
Controller.js
var myApp = angular.module('appHome');
myApp.controller("ctrlEmployeeAdd", ['$scope', 'CommonFunctionFactory', function ($scope, CommonFunctionFactory) {
CommonFunctionFactory.AddMasterData($scope.md_reqnumber)
.then(function (dataSuccess) {
$scope.number = dataSuccess;
}, function (dataError) {
});
}]);
CommonFunctionFactory.js
var appService = angular.module('appHome');
appService.factory('CommonFunctionFactory', ['MetadataOrgFactory', function (MetadataOrgFactory) {
var dataFactory = {};
dataFactory.AddMasterData = function (objData) {
MetadataOrgFactory.postApiCall('addemployee', objData, function (dataSuccess) {
alert("The request has been completed succesfully");
return dataSuccess;
}, function (dataError) {
});
}
return dataFactory;
}])
ApiCallService.js
var appService = angular.module('appHome');
appService.factory('MetadataOrgFactory', ['$http', '$q', function ($http, $q) {
var dataFactory = {};
var url = 'http://localhost:XXXXX';
dataFactory.postApiCall = function (controllerName, objData, callbackSuccess, callbackError) {
$http.post(url + '/api/' + controllerName, objData).then
(function success(response) {
alert("Success");
callbackSuccess(response.data);
}, function error(response) {
callbackError(response.status);
});
};
return dataFactory;
}])
WebApi.cs
[Authorize]
[Route("api/addemployee")]
[HttpPost]
public int AddEmployee(EmployeeViewModel vmEmployee)
{
return 54302 //Just for simplicity here, returning hard coded value
}
I am able to get the above hard coded value in ApiCallService.js and also able to get in the datasuccess variable of CommonFunctionFactory.js but not able to return the value back to Controller.js. This is my actual problem here.
Also angular promise defined in Controller.js is giving following error after getting the response from Web API:-
Cannot read property 'then' of undefined
Please help me in solving the problem.
As AnthW pointed out, you are pretty close, but I think your issues revolve around what you are and aren't returning. My changes are below, it doesn't require changing your controller.js or server files, only the two factories.
CommonFunctionFactory.js
For this, I would personally remove this factory, but if you want to keep it, I would just return the $http call and this way in your controller you can use then and access the returned variable.
var appService = angular.module('appHome');
appService.factory('CommonFunctionFactory', ['MetadataOrgFactory', function (MetadataOrgFactory) {
var dataFactory = {};
dataFactory.AddMasterData = function (objData) {
return MetadataOrgFactory.postApiCall('addemployee', objData);
}
return dataFactory;
}])
ApiCallService.js
As AnthW pointed out in his answer, you aren't returning the $http call, which is why you are seeing undefined. Also, there is no reason to use callback functions when you are using promises, promises are just a different way to handle the same use case. In this code, I am removing your callbacks and we are returning the promise, and the promise is returning either the success object or the error object.
var appService = angular.module('appHome');
appService.factory('MetadataOrgFactory', ['$http', '$q', function ($http, $q) {
var dataFactory = {};
var url = 'http://localhost:XXXXX';
dataFactory.postApiCall = function (controllerName, objData) {
var promise = $http.post(url + '/api/' + controllerName, objData)
.then(function(response) {
alert("Success");
return response.data;
}, function(response) {
return response.status;
});
return promise;
};
return dataFactory;
}])
So, in conclusion, my changes are to have your ApiCallService.js return the promise created by $http. The CommonFunctionFactory.js will just handle return this promise to the controller. In your controller, you are already expecting a promise, so this returned promise will then be properly accessed in your controller, and you will be able to retrieve the returned value.
The postApiCall function is not returning anything. You need to return $http:
dataFactory.postApiCall = function (controllerName, objData, callbackSuccess, callbackError) {
return $http.post(url + '/api/' + controllerName, objData)
.then(function success(response) {
alert("Success");
callbackSuccess(response.data);
},
function error(response) {
callbackError(response.status);
});
};
You will then need to do the same with MetadataOrgFactory.postApiCall by adding a return in front of it:
return MetadataOrgFactory.postApiCall('addemployee', objData, function (dataSuccess) {
alert("The request has been completed succesfully");
return dataSuccess;
},
function (dataError) {}
);
Now, you have returned the request so that calling CommonFunctionFactory.AddMasterData($scope.md_reqnumber) will have a then method available.
I imagine that it was just returning undefined previously. You can check by logging out the value of CommonFunctionFactory.AddMasterData($scope.md_reqnumber)

How to Handle response from Modals as services in AngularJS

I am new to JavaScript and AngularJS. There are couple things with JS that astounds me. For example I am trying to create a modal service and I found teh following example online. I wish to understand what is happening internally in the specific line where the modal is opened.
$scope.checkout = function (cartObj) {
var modalInstance = $modal.open({
templateUrl : 'assets/menu/directives/payment-processing-modal.tmpl.html',
controller : ["$scope", "$modalInstance", "cartObj", function($scope, $modalInstance, cartObj) {
$scope.submit = function () {
console.log("Submit");
//DB CALL HERE (with promise)
return "well";
};
$scope.cancel = function () {
console.log("Cancel");
$modalInstance.dismiss('cancel');
return "not so well";
};
}],
resolve : { // This fires up before controller loads and templates rendered
cartObj : function() {
return cartObj;
}
}
});
In the Line :
var modalInstance = $modal.open({
What I understand is the the open method is called in the $modal service with a bunch of configurations set up.
No within the controller I have my cartObj that is sent by the consuming view which I will then use to make certain CRUD operations.
My question is :
I want the consuming view to know if there was a success or a failure of teh crud operation and also to return the data. How can I implement this with a call back at the consuming view? This is getting confusing because the "Submit" logic is within the Modals's controller.
1. When I return something from here, I am unable to access it on the consuming end. How do return from within submit call?
2. How do I handle success and error based on this setup? Do I handle success and failure at modal service and return just data? Or is there a way I can do it gracefully at consuming end, like:
modalService.checkout(cartObj).success(function(response){
//success handler
}).error(function(response)){
//failure handler
}
As per your code, you are using AngularUI
So, I will just complete your code, which is a way to solve your problem
$scope.checkout = function(cartObj) {
var modalInstance = $modal.open({
templateUrl: 'assets/menu/directives/payment-processing-modal.tmpl.html',
controller: ["$scope", "$uibModalInstance", "cartObj", function($scope, $uibModalInstance, cartObj) {
$scope.submit = function() {
console.log("Submit");
//DB CALL HERE (with promise)
DB_CALL.then(function(success) {
// It resolve and pass to success fuction of the result promise
$uibModalInstance.close({ success: success });
}, function(err) {
// It rejects the result promise
$uibModalInstance.dismiss({ error: err });
});
return "well";
};
$scope.cancel = function() {
console.log("Cancel");
// It rejects the result promise
$uibModalInstance.dismiss({ error: 'cancel' });
return "not so well";
};
}],
resolve: { // This fires up before controller loads and templates rendered
cartObj: function() {
return cartObj;
}
}
}).result;
}
first of all use $uibModalInstance instead modalInstance. Read this
Now what I have done is binded modalInstance with the "result" method provided by $modal.open(), which eventually returns a Promise,
So now you can resolve the promise like this
modalIntance.then(function(success) {
/* Your success code goes here*/
}, function(err) {
/* Your failure code goes here*/
});
Hope it helps.
I figured out an old school way to approach this issue. I did not have to use promise at all. From the consumer end I just return two functions in params like so at the consuming end :
service.method(param, function(result){
//success handler
}, function(err){
//error handler
});
in the modal service, the signature changes as follows:
service.method(params, succesFunc, errorFunc){
//modal code
}
Now I will just call successFunc or errorFunc call backs when I need to based on whether the DB call was a success or not and pass the data or error message respectively in the function's param. Like:
$scope.submit = function() {
console.log("Submit");
//DB CALL HERE (with promise)
DB_CALL.then(function(success) {
// It resolve and pass to success fuction of the result promise
successFunc(success);
}, function(err) {
// It rejects the result promise
errorFunc(err);
});
return "well";
};
Hope this helps someone in this kind of use case.

Using $http callback inside AngularJS service

I have a simple Angular service that uses $http to make a call to an API.
app.service("MyService", function($http){
this.api = function(obj){
return $http.post("/some-route", obj).success(function(data){
//process data in various ways here
var returnObj = {
complete: true,
data: data
};
return returnObj;
});
}
});
In the $http callback, I process the data before returning it. When I call this service in my controller, I want to get that processed data.
The following only gives me the unprocessed data:
MyService.api(someObj).success(function(data){
console.log(data);
});
How do I get the processed data from the callback?
The success function does not create a new promise, so your controller success callback is registered to the same promise as the service (the original one).
Instead you can use then, so it will create a new promise which will be resolved with your returnObj object:
// service
return $http.post("/some-route", obj).then(function(data){
// controller
myService.api().then(function(data) {
I have tested up your code in a plunker, and guess what? Its working for me. Can you please confirm it, or send me more info, i'm glad if i could help.
Plunker
var app = angular.module('plunker', []);
app.service("MyService", function($http){
this.api = function(obj){
return $http.post("http://jsonplaceholder.typicode.com/posts", obj).success(function(data){
//process data in various ways here
console.log(data);
var returnObj = {
complete: true,
data: data
};
return returnObj;
});
}
});
app.controller('MainCtrl', function($scope,MyService) {
$scope.data = 'World';
MyService.api({oi: true}).success(function(data){
$scope.data = data
});
});
Update:
I have misunderstood your question. You want to process the data in the callback to manipulate it in your action. Your code dont work because success() actually returns a promise, but it dont change it, it returns the original one. The one to go for is the then(), which is chainable and returns the modified version of the promise.
I've made changes to the plunker to reflect my new vision of the scenario. Here is the new code.
Thanks for your time.
var app = angular.module('plunker', []);
app.service("MyService", function($http){
this.api = function(obj){
return $http.post("http://jsonplaceholder.typicode.com/posts", obj).then(function(data){
//process data in various ways here
console.log(data);
var returnObj = {
complete: true,
data: data
};
return returnObj;
});
}
});
app.controller('MainCtrl', function($scope,MyService) {
$scope.data = 'World';
MyService.api({oi: true}).then(function(data){
$scope.data = data
});
});
New Plunker

loading JSON data from angular service

Please forgive me if this is a simply problem for an angular guru, i am fairly new to services.
Below is a snippet of my controller where i have attempted make a service request to call out data from my JSON file "jobs.json".
I am not receiving an data when i load my web page neither i am seeing the JSON file in inspector element.
I assume there's something incorrect in my below code. Does anyone what the issue is?
Click here if you need to play about with the code
"use strict";
var app = angular.module("tickrApp", []);
app.service("tickrService", function ($http, $q){
var deferred = $q.defer();
$http.get('app/data/items.json').then(function (data){
deferred.resolve(data);
});
this.getItems = function () {
return deferred.promise;
}
})
.controller('tickCtrl', function($scope, tickrService) {
var promise = tickrService.getItems();
promise.then(function (data){
$scope.items= getData;
console.log($scope.items);
});
In your Plunkr, you had a few errors, such as the <script> tags around the wrong way (you need to have Angular first, so your code can then use angular.module). You also had the wrong attribute of ng-app-data instead of data-ng-app.
The key problem was with the JS code, the first parameter to the success handler for the $http.get() call is an object with a data property, which is the actual data returned. So you should resolve your promise with that property instead.
Then in the controller, like Michael P. said, getData is undefined, you should use the data parameter passed in.
app.service("tickrService", function($http, $q) {
var deferred = $q.defer();
$http.get('jobs.json').then(function(response) {
deferred.resolve(response.data);
});
this.getjobs = function() {
return deferred.promise;
}
})
.controller('tickCtrl', function($scope, tickrService) {
var promise = tickrService.getjobs();
promise.then(function(data) {
$scope.jobs = data;
console.log($scope.jobs);
});
});
See forked Plunkr.
In the success handler of your getItems function, you are storing getData, which is undefined. You want to store data instead.
Therefore, in the controller, your call to getItems() should be as follows
tickrService.getItems().then(function (data) {
$scope.items = data;
});
Also, you want to make the $http call in getItems. Like that :
this.getItems = function () {
var deferred = $q.defer();
$http.get('app/data/items.json').then(function (data) {
deferred.resolve(data);
});
return deferred.promise;
}
However, you can avoid the above boilerplate code around the promises, because $http.get returns itself a promise. Your service and controller could be much more concise and less polluted by boilerplate code.
The service could be as simple as :
app.service("tickrService", function ($http) {
this.getItems = function () {
return $http.get('app/data/items.json');
}
});
And the controller could be shortened to:
app.controller('tickCtrl', function ($scope, tickrService) {
tickrService.getItems().then(function (response) {
$scope.items = response.data;
})
});
Please note that the response resolved by $http is an object that contains (link to doc) :
data – The response body transformed with the transform functions.
status – HTTP status code of the response.
headers – {function([headerName])} – Header getter function.
config – The configuration object that was used to generate the request.
statusText – HTTP status text of the response.
Therefore in the success handler of getItems we are storing response.data, which is the response body, and not the whole response object.

Processing $http response in service

I recently posted a detailed description of the issue I am facing here at SO. As I couldn't send an actual $http request, I used timeout to simulate asynchronous behavior. Data binding from my model to view is working correct, with the help of #Gloopy
Now, when I use $http instead of $timeout (tested locally), I could see the asynchronous request was successful and data is filled with json response in my service. But, my view is not updating.
updated Plunkr here
Here is a Plunk that does what you want: http://plnkr.co/edit/TTlbSv?p=preview
The idea is that you work with promises directly and their "then" functions to manipulate and access the asynchronously returned responses.
app.factory('myService', function($http) {
var myService = {
async: function() {
// $http returns a promise, which has a then function, which also returns a promise
var promise = $http.get('test.json').then(function (response) {
// The then function here is an opportunity to modify the response
console.log(response);
// The return value gets picked up by the then in the controller.
return response.data;
});
// Return the promise to the controller
return promise;
}
};
return myService;
});
app.controller('MainCtrl', function( myService,$scope) {
// Call the async method and then do stuff with what is returned inside our own then function
myService.async().then(function(d) {
$scope.data = d;
});
});
Here is a slightly more complicated version that caches the request so you only make it first time (http://plnkr.co/edit/2yH1F4IMZlMS8QsV9rHv?p=preview):
app.factory('myService', function($http) {
var promise;
var myService = {
async: function() {
if ( !promise ) {
// $http returns a promise, which has a then function, which also returns a promise
promise = $http.get('test.json').then(function (response) {
// The then function here is an opportunity to modify the response
console.log(response);
// The return value gets picked up by the then in the controller.
return response.data;
});
}
// Return the promise to the controller
return promise;
}
};
return myService;
});
app.controller('MainCtrl', function( myService,$scope) {
$scope.clearData = function() {
$scope.data = {};
};
$scope.getData = function() {
// Call the async method and then do stuff with what is returned inside our own then function
myService.async().then(function(d) {
$scope.data = d;
});
};
});
Let it be simple. It's as simple as
Return promise in your service(no need to use then in service)
Use then in your controller
Demo. http://plnkr.co/edit/cbdG5p?p=preview
var app = angular.module('plunker', []);
app.factory('myService', function($http) {
return {
async: function() {
return $http.get('test.json'); //1. this returns promise
}
};
});
app.controller('MainCtrl', function( myService,$scope) {
myService.async().then(function(d) { //2. so you can use .then()
$scope.data = d;
});
});
Because it is asynchronous, the $scope is getting the data before the ajax call is complete.
You could use $q in your service to create promise and give it back to
controller, and controller obtain the result within then() call against promise.
In your service,
app.factory('myService', function($http, $q) {
var deffered = $q.defer();
var data = [];
var myService = {};
myService.async = function() {
$http.get('test.json')
.success(function (d) {
data = d;
console.log(d);
deffered.resolve();
});
return deffered.promise;
};
myService.data = function() { return data; };
return myService;
});
Then, in your controller:
app.controller('MainCtrl', function( myService,$scope) {
myService.async().then(function() {
$scope.data = myService.data();
});
});
tosh shimayama have a solution but you can simplify a lot if you use the fact that $http returns promises and that promises can return a value:
app.factory('myService', function($http, $q) {
myService.async = function() {
return $http.get('test.json')
.then(function (response) {
var data = reponse.data;
console.log(data);
return data;
});
};
return myService;
});
app.controller('MainCtrl', function( myService,$scope) {
$scope.asyncData = myService.async();
$scope.$watch('asyncData', function(asyncData) {
if(angular.isDefined(asyncData)) {
// Do something with the returned data, angular handle promises fine, you don't have to reassign the value to the scope if you just want to use it with angular directives
}
});
});
A little demonstration in coffeescript: http://plunker.no.de/edit/ksnErx?live=preview
Your plunker updated with my method: http://plnkr.co/edit/mwSZGK?p=preview
A much better way I think would be something like this:
Service:
app.service('FruitsManager',function($q){
function getAllFruits(){
var deferred = $q.defer();
...
// somewhere here use: deferred.resolve(awesomeFruits);
...
return deferred.promise;
}
return{
getAllFruits:getAllFruits
}
});
And in the controller you can simply use:
$scope.fruits = FruitsManager.getAllFruits();
Angular will automatically put the resolved awesomeFruits into the $scope.fruits.
I had the same problem, but when I was surfing on the internet I understood that $http return back by default a promise, then I could use it with "then" after return the "data". look at the code:
app.service('myService', function($http) {
this.getData = function(){
var myResponseData = $http.get('test.json').then(function (response) {
console.log(response);.
return response.data;
});
return myResponseData;
}
});
app.controller('MainCtrl', function( myService, $scope) {
// Call the getData and set the response "data" in your scope.
myService.getData.then(function(myReponseData) {
$scope.data = myReponseData;
});
});
When binding the UI to your array you'll want to make sure you update that same array directly by setting the length to 0 and pushing the data into the array.
Instead of this (which set a different array reference to data which your UI won't know about):
myService.async = function() {
$http.get('test.json')
.success(function (d) {
data = d;
});
};
try this:
myService.async = function() {
$http.get('test.json')
.success(function (d) {
data.length = 0;
for(var i = 0; i < d.length; i++){
data.push(d[i]);
}
});
};
Here is a fiddle that shows the difference between setting a new array vs emptying and adding to an existing one. I couldn't get your plnkr working but hopefully this works for you!
Related to this I went through a similar problem, but not with get or post made by Angular but with an extension made by a 3rd party (in my case Chrome Extension).
The problem that I faced is that the Chrome Extension won't return then() so I was unable to do it the way in the solution above but the result is still Asynchronous.
So my solution is to create a service and to proceed to a callback
app.service('cookieInfoService', function() {
this.getInfo = function(callback) {
var model = {};
chrome.cookies.get({url:serverUrl, name:'userId'}, function (response) {
model.response= response;
callback(model);
});
};
});
Then in my controller
app.controller("MyCtrl", function ($scope, cookieInfoService) {
cookieInfoService.getInfo(function (info) {
console.log(info);
});
});
Hope this can help others getting the same issue.
I've read http://markdalgleish.com/2013/06/using-promises-in-angularjs-views/
[AngularJS allows us to streamline our controller logic by placing a promise directly on the scope, rather than manually handing the resolved value in a success callback.]
so simply and handy :)
var app = angular.module('myApp', []);
app.factory('Data', function($http,$q) {
return {
getData : function(){
var deferred = $q.defer();
var promise = $http.get('./largeLoad').success(function (response) {
deferred.resolve(response);
});
// Return the promise to the controller
return deferred.promise;
}
}
});
app.controller('FetchCtrl',function($scope,Data){
$scope.items = Data.getData();
});
Hope this help
I really don't like the fact that, because of the "promise" way of doing things, the consumer of the service that uses $http has to "know" about how to unpack the response.
I just want to call something and get the data out, similar to the old $scope.items = Data.getData(); way, which is now deprecated.
I tried for a while and didn't come up with a perfect solution, but here's my best shot (Plunker). It may be useful to someone.
app.factory('myService', function($http) {
var _data; // cache data rather than promise
var myService = {};
myService.getData = function(obj) {
if(!_data) {
$http.get('test.json').then(function(result){
_data = result.data;
console.log(_data); // prove that it executes once
angular.extend(obj, _data);
});
} else {
angular.extend(obj, _data);
}
};
return myService;
});
Then controller:
app.controller('MainCtrl', function( myService,$scope) {
$scope.clearData = function() {
$scope.data = Object.create(null);
};
$scope.getData = function() {
$scope.clearData(); // also important: need to prepare input to getData as an object
myService.getData($scope.data); // **important bit** pass in object you want to augment
};
});
Flaws I can already spot are
You have to pass in the object which you want the data added to, which isn't an intuitive or common pattern in Angular
getData can only accept the obj parameter in the form of an object (although it could also accept an array), which won't be a problem for many applications, but it's a sore limitation
You have to prepare the input object $scope.data with = {} to make it an object (essentially what $scope.clearData() does above), or = [] for an array, or it won't work (we're already having to assume something about what data is coming). I tried to do this preparation step IN getData, but no luck.
Nevertheless, it provides a pattern which removes controller "promise unwrap" boilerplate, and might be useful in cases when you want to use certain data obtained from $http in more than one place while keeping it DRY.
As far as caching the response in service is concerned , here's another version that seems more straight forward than what I've seen so far:
App.factory('dataStorage', function($http) {
var dataStorage;//storage for cache
return (function() {
// if dataStorage exists returned cached version
return dataStorage = dataStorage || $http({
url: 'your.json',
method: 'GET',
cache: true
}).then(function (response) {
console.log('if storage don\'t exist : ' + response);
return response;
});
})();
});
this service will return either the cached data or $http.get;
dataStorage.then(function(data) {
$scope.data = data;
},function(e){
console.log('err: ' + e);
});
Please try the below Code
You can split the controller (PageCtrl) and service (dataService)
'use strict';
(function () {
angular.module('myApp')
.controller('pageContl', ['$scope', 'dataService', PageContl])
.service('dataService', ['$q', '$http', DataService]);
function DataService($q, $http){
this.$q = $q;
this.$http = $http;
//... blob blob
}
DataService.prototype = {
getSearchData: function () {
var deferred = this.$q.defer(); //initiating promise
this.$http({
method: 'POST',//GET
url: 'test.json',
headers: { 'Content-Type': 'application/json' }
}).then(function(result) {
deferred.resolve(result.data);
},function (error) {
deferred.reject(error);
});
return deferred.promise;
},
getABCDATA: function () {
}
};
function PageContl($scope, dataService) {
this.$scope = $scope;
this.dataService = dataService; //injecting service Dependency in ctrl
this.pageData = {}; //or [];
}
PageContl.prototype = {
searchData: function () {
var self = this; //we can't access 'this' of parent fn from callback or inner function, that's why assigning in temp variable
this.dataService.getSearchData().then(function (data) {
self.searchData = data;
});
}
}
}());

Categories