I've got a working $http.POST request in a factory whose job is to get a list object and return it. Simple as that, except the object property I'm returning it to just grows legs and leaves. I'll explain:
just the problem bit for right now, everything else seems to be working fine though
$scope.updateObj = function(num) {
console.log("Obj before:\n" + JSON.stringify($scope.Obj));
$scope.Obj.name = "Obj_" + num;
$scope.Obj.list = myFactory.getList($scope.Obj.name);
window.setTimeout(console.log("Obj after:\n" + JSON.stringify($scope.Obj)), 3000);
};
The console.log before the update displays the object just as defined in myController.js, and the console.log after the update has the correct updated information, but is missing the list property altogether.
I set the the console.log checking Obj after the update on a timeout to see if the request just needed a little more time, but I don't think it's working as intended. I'm thinking it's an asynchronous problem, but I'm still fairly new, and I don't have a good grasp on how to use the $q service.
tld;dr: How do I asynchronously attach response.data from $http to an object property?
myFactory.js
app.factory('myFactory', function($http) {
var service = {};
service.getList = function(name) {
try {
console.log("getting" + name);
var temp = $http({
method: 'POST',
url: 'yourmom.com',
headers: {
'Content-Type': 'application/json'
},
data: {list: name},
cache: true
}).then(
function success(response) {
alert(JSON.stringify(response.data.list)); // <- checks out & is exactly what I expect.
return response.data.list;
},
function error(response) {
throw error(response.status);
}
);
}
catch(err) {
alert(err);
return undefined;
}
};
return service;
});
myController.js
app.controller('GUIcontroller', ['$scope', 'myFactory', 'networkFactory', function($scope, myFactory, networkFactory) {
$scope.number = undefined;
$scope.networkInit = networkFactory.init();
$scope.Obj = {
id: 0,
name: "",
list: {}
};
$scope.updateObj = function(num) {
console.log("Obj before:\n" + JSON.stringify($scope.Obj));
$scope.Obj.name = "Obj_" + num;
$scope.Obj.list = myFactory.getList($scope.Obj.name);
console.log("Obj after:\n" + JSON.stringify($scope.Obj));
};
}]);
You need to think about promises differently. The main benefit of having promises is that you get the result exactly when it's done, whether it's successful or throws an error.
This is a proposed way of doing what you're trying to achieve:
app.factory('myFactory', function($http) {
var service = {};
service.getList = function(name) {
return $http({
method: 'POST',
url: 'yourmom.com',
headers: {
'Content-Type': 'application/json'
},
data: {list: name},
cache: true
});
};
return service;
});
app.controller('GUIcontroller', ['$scope', 'myFactory', 'networkFactory', function($scope, myFactory, networkFactory) {
$scope.number = undefined;
$scope.networkInit = networkFactory.init();
$scope.Obj = {
id: 0,
name: "",
list: {}
};
$scope.updateObj = function(num) {
$scope.Obj.name = "Obj_" + num;
myFactory.getList($scope.Obj.name).then(function(response) {
$scope.Obj.list = response;
}).catch(function(err) {
console.error('Error fetching list: ', err);
});
};
}]);
Related
I have an AngularJs application working with components and several modules. I created a plunker example to present my problem here.
I have my NavbarComponent where I declared my Controller where I inject my service called NavbarService.
In the NavbarService, I inject a factory resource to make my Rest call, once this call is made I'm trying to made some treatment on the response before returning it back to the controller, in this example I just apply a simple filter on it, but it doesn't work. If I omit my treatment and return only the categories, the code works and you can visualize a list of two.
I can make my treatment in the controller but this is a bad practice 'cause I believe it should be done in the Service, secondly since it's an asynchronous response I must do something like this to make it work, which is really really ugly:
navbarService.getCategories().$promise.then(function (response) {
console.log("controller", response[0].category);
vm.categories = categoryFilter(response[0].category);
}, function (error) {
console.log("an error occured");
});
Can someone please guide me through this, I'm out of solutions. Thank you
Another simple way is to pass a callback function to service from you component like this
'use strict';
angular.module('navbar').component('appNavbar', {
templateUrl: "navbar.template.html",
controller: [ 'navbarService', function appNavbarController(navbarService) {
var vm = this;
navbarService.getCategories(function(data){
// this will be called when service will get the response and filter function has filtered the response
vm.categories = data;
});
}]
});
Now service should be like this
'use strict';
angular.module('navbar').service('navbarService', ['categoryResourceService', 'categoryFilter', function(categoryResourceService, categoryFilter) {
var vm = this;
vm.getCategories = function(callback) {
categoryResourceService.query(function(response) {
console.log("[NavbarService] response:", response);
callback(categoryFilter(response));
}, function(error) {
console.log("[NavbarService] error:", error);
});
//return vm.categories;
}
}]);
Filter will be like this
'use strict';
angular.module('navbar').filter('category', function() {
return function(categories) {
var categoryIds = ['World'];
var result = [];
angular.forEach(categoryIds, function (categoryId) {
angular.forEach(categories, function (category) {
if (category.name == categoryId) {
console.log("Match");
result.push(category);
}
});
});
return result;
};
});
Your filter should be like this and it should be called in transformResponse in $resource query instead of service, i hope this will help you
'use strict';
angular.module('navbar').filter('category', function() {
return function(categories) {
var categoryIds = ['World'];
var result = [];
angular.forEach(categoryIds, function (categoryId) {
angular.forEach(categories, function (category) {
if (category.name == categoryId) {
console.log("Match");
result.push(category);
}
});
});
return result;
};
});
Your categoryResource.service should be like this
angular.module('shared').factory('categoryResourceService',
['$resource','categoryFilter', function($resource, categoryFilter) {
var provider = "categories.json";
var params = {
id: '#id'
};
return $resource(provider, params, {
query: {
isArray: true,
method: 'GET',
params: {},
transformResponse: function(categories) {
var results = categoryFilter(angular.fromJson(categories));
console.log("[categoryResourceService] filtered response:", results);
return results;
}
}
});
}]);
navbar.service should be like this simply
'use strict';
angular.module('navbar')
.service('navbarService', [ 'categoryResourceService', function (categoryResourceService) {
var vm = this;
vm.getCategories = function(){
vm.categories = categoryResourceService.query(function(response){
console.log("[NavbarService] response:", response);
}, function(error){
console.log("[NavbarService] error:", error);
});
return vm.categories;
}
}]);
And components like this
'use strict';
angular.module('navbar').component('appNavbar', {
templateUrl: "navbar.template.html",
controller: [ 'navbarService', function appNavbarController(navbarService) {
var vm = this;
vm.categories = navbarService.getCategories();
}]
});
I'm using a deferred promise because I need the app to wait for the result of an asynchronous call. The function retrieves the correct data, but I can't seem to get that back to my controller.
Specifically, the line of code
data : multiBarChartData
is not getting the resolved data from
function multiBarChartData()
Here is the factory:
angular.module('hapDash.services', []).factory('DataService', function ($http, $q, SESSION_ID) {
return {
multiBarChart : {
data : multiBarChartData
}
};
function multiBarChartData() {
var deferred = $q.defer();
$http({
method : 'GET',
url : '/services/data/v37.0/analytics/reports/00OP0000000Q9iB',
headers : {
'Authorization' : 'Bearer ' + SESSION_ID
}
}).success(function (data) {
var report02ChartData = [];
angular.forEach(data.groupingsDown.groupings, function (de, di) {
var report02Values = [];
report02ChartData.push({'key' : de.label, 'values' : report02Values});
angular.forEach(data.groupingsAcross.groupings, function (ae, ai) {
report02Values.push({'x' : ae.label, 'y' : data.factMap[de.key + '!' + ae.key].aggregates[0].value});
});
});
data = report02ChartData;
deferred.resolve(data);
}).error(function () {
deferred.reject('There was an error accessing the Analytics API')
})
return deferred.promise;
}
});
... and here is the controller:
var app = angular.module('hapDash', ['config', 'nvd3', 'gridster', 'hapDash.services']);
app.controller('HapDashCtrl', function ($scope, $timeout, DataService) {
$scope.dashboard = {
widgets : [{
col : 0,
row : 0,
sizeY : 1,
sizeX : 1,
name : "Multi Bar Chart",
chart : {
data : DataService.multiBarChart.data(),
api : {}
}
}
]
};
});
I'm trying to get the controller code
data : DataService.multiBarChart.data(),
to pull the async response once it is complete.
What am I doing wrong?
DataService.multiBarChart.data()
returns a promise and not the real data. You can access the real data in the controller via:
DataService.multiBarChart.data().then(
function(data) {
/*do something with data*/
}
).catch(
function(err) {
/*handle error*/
}
);
Furthermore you're falling for a famous Promise anti-pattern here by not using the promise returned by the $http service and therefor breaking the promise chain. The following snippet wouldn't break the promise chain and would let you handle any error that might occur at a single catch block.
function multiBarChartData() {
return $http(/*...*/).then(
function (response) {
var report02ChartData = [];
.success(function (data) {
var report02ChartData = [];
angular.forEach(response.data.groupingsDown.groupings, function (de, di) {
/*...*/
});
});
data = report02ChartData;
return data;
});
}
This way the $http errors are also emitted in the .catch(fuction(err){..}) block in the controller, because you didn't break the chain.
UPDATE:
Since .success/.error are not chainable and marked deprecated, you should use .then/.error instead.
You need to initialize $scope.dashboard object when promise is resolved:
app.controller('HapDashCtrl', function($scope, $timeout, DataService) {
DataService.multiBarChart.data().then(function(data) {
$scope.dashboard = {
widgets: [{
col: 0,
row: 0,
sizeY: 1,
sizeX: 1,
name: "Multi Bar Chart",
chart: {
data: data,
api: {}
}
}]
};
});
});
app.controller('PageCtrl', ['$scope', '$stateParams', '$state', 'USER_ROLES', 'AuthService', function($scope,$stateParams,$state,USER_ROLES, AuthService){
//console.log("Page Controller reporting for duty.");
$scope.currentUser = null;
$scope.currentUserExists = false; //<- defined in scope of PageCtrl
$scope.userRoles = USER_ROLES;
$scope.isAuthorized = AuthService.isAuthorized;
$scope.setCurrentUser = function (user) {
$scope.currentUser = user;
$scope.currentUserExists = true; //<- set true here!!
};
Now in my html code I'm doing:
<body ng-app="myApp" ng-controller="PageCtrl">
....
<div class="navbar-brand" ng-if="currentUserExists">Welcome!!</div>
<div class="navbar-brand" ng-if="currentUser.data.teacher._id">Welcome2!!</div>
Iv tried ng-show, and I'm trying both examples above to test it.
If i log to console currentUser within my js file everything appears to be working normal. But nothing will show on my page.
Am i missing something??
currentUser is json set like:
$scope.login = function (credentials) {
AuthService.login(credentials).then(function (user) {
$rootScope.$broadcast(AUTH_EVENTS.loginSuccess);
$scope.setCurrentUser(user);
console.log("currentuser");
console.log($scope.currentUser.data.teacher._id); //Yes its working...
}, function () {
$rootScope.$broadcast(AUTH_EVENTS.loginFailed);
});
};
...
authService.login = function (credentials) {
return $http({
method: 'POST',
url: 'proxy2.php',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
transformRequest: function(obj) {
var str = [];
for(var p in obj)
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
return str.join("&");
},
data: {
url: 'http://teach.classdojo.com/api/dojoSession?_u=tid',
login: credentials.login,
password: credentials.password,
type: 'teacher'
}
}).success(function (res) {
alert("login success");
Session.create(res.teacher._id, "admin");
return res.teacher;
}).error(function (res) {
alert(res.error.detail);
});
};
You may want to try this:
<div class="navbar-brand" ng-if="currentUserExists==true">Welcome!!</div>
The problem may be due to your setting a primitive value on the scope.
Try using view.currentUserExists instead- so set $scope.view = {currentUserExists: false} in the initialization and then $scope.view.currentUserExists = true in the promise resolution.
I couldn't understand why the scope wasn't working on the page, apparently due to inheritence or maybe because i was using $scope in two controllers?
Anyway i solved it using a service:
$scope.login = function (credentials) {
AuthService.login(credentials).then(function (user) {
userService.setCurrentUser(user); //<----Added this
$rootScope.$broadcast(AUTH_EVENTS.loginSuccess);
}, function () {
$rootScope.$broadcast(AUTH_EVENTS.loginFailed);
});
};
...
var updateUser = function() {
scope.view.currentUser = userService.getCurrentUser();
};
scope.$on(AUTH_EVENTS.loginSuccess, updateUser);
..
app.service('userService', function() {
var currentUser = null;
return {
setCurrentUser: function(user) {
currentUser = user;
},
getCurrentUser: function() {
return currentUser;
}
};
});
Hello guys I have the following code here :
angular.module('todomvc')
.factory('todoStorage', function ($http) {
'use strict';
var STORAGE_ID = 'todos-angularjs';
return {
get: function () {
$http({method: 'GET', url: '/api/todo.php'}).
success(function(data, status, headers, config) {
// this callback will be called asynchronously
// when the response is available
return JSON.stringify(data);
}).
error(function(data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
},
put: function (todos) {
debugger;
localStorage.setItem(STORAGE_ID, JSON.stringify(todos));
}
};
});
as well as
angular.module('todomvc')
.controller('TodoCtrl', function TodoCtrl($scope, $routeParams, $filter, todoStorage, $http) {
'use strict';
var todos = $scope.todos = todoStorage.get();
$scope.newTodo = '';
$scope.editedTodo = null;
$scope.$watch('todos', function (newValue, oldValue) {
$scope.remainingCount = $filter('filter')(todos, { completed: false }).length;
$scope.completedCount = todos.length - $scope.remainingCount;
$scope.allChecked = !$scope.remainingCount;
if (newValue !== oldValue) { // This prevents unneeded calls to the local storage
todoStorage.put(todos);
}
}, true);
The issue i have is that the HTTP request is not completing before the code at $scope.$watch is being executed therefore it is calling .length on undefined. I am a total n00b to Angular and wanted to use this TodoMVC to get it working however im not sure what i can do to halt the whole process instead of wrapping the rest of the code within the success callback from the http request.
Thanks in advance
Issue
#1 you need to return promise from your get method of your factory, and use $http.then instead of http's custom promise method success.
#2 you need to chain it though to assign value to the scope property.
#3 when you watch asyncronously assigned properties you need to have a null check because the watch is going to run when the controller is set up.
#4 I am not sure if you should do JSON.stringify the response because looks like you are in need of an array data?
In your factory
return {
get: function () {
return $http({method: 'GET', url: '/api/todo.php'}). //Return here
then(function(response) { //
return response.data;
}, function(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
},
In your controller:-
var todos;
todoStorage.get().then(function(data) {
todos = $scope.todos = data
});
and
$scope.$watch('todos', function (newValue, oldValue) {
if(!newValue) return;
$scope.remainingCount = $filter('filter')(todos, { completed: false }).length;
$scope.completedCount = todos.length - $scope.remainingCount;
$scope.allChecked = !$scope.remainingCount;
if (newValue !== oldValue) { // This prevents unneeded calls to the local storage
todoStorage.put(todos);
}
}, true);
use this code:
angular.module('todomvc')
.factory('todoStorage', function ($http) {
'use strict';
var STORAGE_ID = 'todos-angularjs';
return {
get: function () {
return $http({method: 'GET', url: '/api/todo.php'})
},
put: function (todos) {
debugger;
localStorage.setItem(STORAGE_ID, JSON.stringify(todos));
}
};
});
angular.module('todomvc')
.controller('TodoCtrl', function TodoCtrl($scope, $routeParams, $filter, todoStorage, $http) {
'use strict';
$scope.newTodo = '';
$scope.editedTodo = null;
todoStorage.get()
.then(function(d){
$scope.todos=d;
})
.catch(function(e){
console.error(e);
})
$scope.$watch('todos', function (newValue, oldValue) {
$scope.remainingCount = $filter('filter')(todos, { completed: false }).length;
$scope.completedCount = todos.length - $scope.remainingCount;
$scope.allChecked = !$scope.remainingCount;
if (newValue !== oldValue) { // This prevents unneeded calls to the local storage
todoStorage.put(todos);
}
}, true);
$http is wrapped in a promise. You could return the promise from the service:
return $http({method: 'GET', url: '/api/todo.php'})
In the controller you can use then method to specify the behaviour when the request is completed:
$scope.newTodo = '';
$scope.editedTodo = null;
todoStorage.get().then(function(result) {
$scope.todos = result.data;
$scope.$watch('todos', function (newValue, oldValue) {
$scope.remainingCount = $filter('filter')($scope.todos, { completed: false }).length;
$scope.completedCount = todos.length - $scope.remainingCount;
$scope.allChecked = !$scope.remainingCount;
if (newValue !== oldValue) { // This prevents unneeded calls to the local storage
todoStorage.put($scope.todos);
}
}, true);
});
A good example about how to chain promises:
http://codepen.io/willh/pen/fCtuw
I'm newbie in angularjs and I'm trying to create new provider. This is my code:
myApp.provider('$Data', function() {
this.URL = 'http://maps.googleapis.com/maps/api/geocode/json?address=Singapore, SG, Singapore, 153 Bukit Batok Street 1&sensor=true';
this.$get = $get;
$get.$inject = ['$http', '$q'];
function $get($http, $q) {
var that = this;
return {
isConnected: function() {
var bIsConnected = 'Default';
$http({method: 'GET', url:that.URL}).then(function (data) {
bIsConnected = 'Yes';
alert('Run this code!');
}, function (data) {
bIsConnected = 'No';
});
return bIsConnected;
}
}
}
});
Jsfiddle demo:
http://jsfiddle.net/0udm9/9dPsb/6/
After I run $Data.isConnected(), the result is always 'Default' although browser show the alert box. I think it's from success function is not of $get. And I have to use provider, not service or factory for this case. Can I do anything to fix this issue?
Thanks,
You have to use promise in your code.
DEMO
Provider:
isConnected: function() {
var deferred = $q.defer();
$http.get(that.url).then(function(res) {
deferred.resolve('Yes');
console.log('example:success', res);
}, function(err) {
deferred.resolve('No');
console.log('example:error', err);
});
return deferred.promise;
}
Controller:
$Data.isConnected().then(function(data) {
$scope.data = data;
});
// UPD
You must use objects if you need to use return values with async code.
DEMO
// UPD 2
FRESH DEMO LINK