I have a page where I need to hit 2 restful web service calls. 1st rest call is successful and I am getting back the data. After hitting 2nd service, still the data of 1st call is persisted in the variable. So using call back method is the solution for this?If so, how to write callback method in angularjs way?
Here is my code.
app.directive('collection', function() {
return {
restrict: "E",
replace: true,
scope: {
collection: '=',
articleData: '=',
articleContent: '='
},
template: "<ul><member ng-repeat='member in collection' member='member' article-data='articleData' article-content='articleContent'></member></ul>"
}
});
app.directive('member', function($compile,$http,getTocService) {
return {
restrict: "A",
replace: true,
scope: {
member: '=',
articleData: '=',
articleContent: '='
},
template: "<div><li><a href='#' ng-click='getContent(member.itemId)'>{{member.title}}</a></li></div>",
link: function(scope, element, attrs) {
scope.getContent = function(itemId) {
var art = getTocService.getArtData(itemId);
}
if (angular.isArray(scope.member.tocItem)) {
if (scope.member.hasChildren == "true") {
for (var i = 0; i < scope.member.tocItem.length; i++) {
if (scope.member.tocItem.title) {
scope.member.tocItem.title.hide = true;
}
}
}
element.append("<collection collection='member.tocItem'></collection>");
$compile(element.contents())(scope)
}
}
}
});
app.controller('apdController', function($scope, getTocService,$location) {
var bookId = $location.search().id;
var sampdata = getTocService.getToc(bookId);
$scope.tasks =sampdata;
// $scope.tasks = data;
// var artData = getTocService.getArtData('PH1234');
// $scope.articleContent = artData;
});
app.service(
"getTocService",
function( $http, $q ) {
return({
getToc: getToc,
getArtData: getArtData
});
function getToc(bookIdvar) {
var request = $http({
method: "post",
url: "http://10.132.241.41:8082/apdpoc/services/ApdBookService/getTOC",
params: {
action: "post"
},
data: {
getTOCCriteria:{
bookId: bookIdvar
}
}
});
return( request.then(handleSuccess,handleError));
}
function getArtData(itemId) {
var request = $http({
method: "post",
url: "http://10.132.241.41:8082/apdpoc/services/ApdBookService/getArticle",
params: {
action: "post"
},
data: {
getArticleCriteria:{
articleId: itemId,
locale: "en_US"
}
}
});
alert(data);
return( request.then(handleSuccess,handleError));
}
function handleSuccess(response){
return (response.data);
}
function handleError( response ) {
if (
! angular.isObject(response.data) ||
! response.data.message
) {
return($q.reject("An unknown error occurred."));
}
return($q.reject(response.data.message));
}
}
);
Here, "data" is the variable I am using in both the calls to hold the response data. And I am calling 2nd service "getArtData" from
var art = getTocService.getArtData(itemId);
You should strongly consider using promises. Promises allow chaining and are a lot better than callback hell. The keyword here is using then.
This SO post explains it better: Processing $http response in service
Hope this is helpful to you.
Your getTocService returns promises and you need to chain the two promises.
var bookId = $location.search().id;
var sampdataPromise = getTocService.getToc(bookId);
sampdataPromise.then( function(data) {
$scope.tasks = data;
//return next promise for chaining
return getTocService.getArtData(data.itemId);
}).then (function (artData) {
$scope.articleContent = artData;
}).catch (function (error) {
//log error
});
Related
I'm working on some legacy code that uses angularjs 1.x for a web frontend. I need to create a modal dialog that will make a RESTful call to the backend when the modal is opened and wait for the data to be returned before rendering the view.
I was able to figure out most of what I needed to do, but there is one thing I still can't wrap my head around. My understanding was that I needed to use 'resolve' to define a function that would return a $promise to the controller. When I put a breakpoint inside my controller though, the parameter is an object containing the promise, the resolution status, and finally my actual data.
I can pull the data I need out of this object, but it feels like I shouldn't have to do that. My controller doesn't care about the promise itself; just the data that got returned. Is there some way to structure this so only the data gets sent to the controller or is this just how angular modals are expected to behave?
A sample of my code:
$scope.openTerritorySelect = function () {
var modalInstance = $modal.open({
animation: true,
templateUrl: 'prospect/detail/selectTerritoriesModal.tpl.html',
controller: function($scope, $modalInstance, availableReps){
$scope.reps = availableReps;
$scope.ok=function()
{
$modalInstance.close();
};
$scope.cancel=function()
{
$modalInstance.dismiss('cancel');
};
},
resolve: {
availableReps: function () {
return Prospect.getRelatedReps({}, function (data, header) {
$scope.busy = false;
return data.result;
}, function (response) {
$scope.busy = false;
if (response.status === 404) {
$rootScope.navError = "Could not get reps";
$location.path("/naverror");
}
}).$promise;
}
}
});
modalInstance.result.then(function (selectedReps) {
}, function () {
console.log('Modal dismissed at: ' + new Date());
});
};
The 'Prospect' service class:
angular.module('customer.prospect', [ "ngResource" ]).factory('Prospect', [ 'contextRoute', '$resource', function(contextRoute, $resource) {
return {
getRelatedReps : function(args, success, fail) {
return this.payload.getRelatedReps(args, success, fail);
},
payload : $resource(contextRoute + '/api/v1/prospects/:id', {
}, {
'getRelatedReps' : {
url : contextRoute + '/api/v1/prospects/territories/reps',
method : 'GET',
isArray : false
}
})
};
} ]);
You could simplify things a great deal by making the REST request before you even open the modal. Would you even want to open the modal if the request were to fail?
$scope.openTerritorySelect = function () {
Prospect.getRelatedReps({}, function (data, header) {
$scope.busy = false;
var modalInstance = $modal.open({
animation: true,
templateUrl: 'prospect/detail/selectTerritoriesModal.tpl.html',
controller: function($scope, $modalInstance, availableReps){
$scope.reps = availableReps;
$scope.ok = function() {
$modalInstance.close();
};
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
};
},
resolve: {
availableReps: function () {
return data.result;
}
});
modalInstance.result.then(function (selectedReps) {},
function () {
console.log('Modal dismissed at: ' + new Date());
});
}, function (response) {
$scope.busy = false;
if (response.status === 404) {
$rootScope.navError = "Could not get reps";
$location.path("/naverror");
}
});
};
I got a valid code full of callback functions, Callback hell.
Here it is :
app.directive('destinationDirective', function () {
function destinationController($http, $rootScope, $scope) {
$scope.getWeather = function (destination) {
var url = 'http://api.openweathermap.org/data/2.5/weather?q=' + destination.city
+ '&appid=' + $scope.apiKey
+ '&units=metric';
$http.get(url).then(function success(response) {
console.log('success', response);
if (response.data.weather) {
destination.weather = {};
destination.weather.main = response.data.weather[0].main;
destination.weather.temp = response.data.main.temp;
} else {
$scope.message = 'City not found';
}
}, function error(error) {
$scope.message = 'Server error'
});
};
}
return {
scope: {
destination: '=',
apiKey: '=',
onRemove: '&'
},
templateUrl: './destinationDirective.html',
controller: destinationController
};
});
So I try to make it flatter with functions definitions like below, but it's seems the execution context can not resolve my destination variable. I try to bind the execution context and it's still can't resolve. Did AngularJS kind of "tweak" the execution context of the function?
app.directive('destinationDirective', function () {
function destinationController($http, $rootScope, $scope) {
$scope.getWeather = function (destination) {
var url = 'http://api.openweathermap.org/data/2.5/weather?q=' + destination.city
+ '&appid=' + $scope.apiKey
+ '&units=metric';
$http.get(url).then(success.bind(destination), error);
};
}
function success(response) {
console.log('success', response);
if (response.data.weather) {
destination.weather = {};
destination.weather.main = response.data.weather[0].main;
destination.weather.temp = response.data.main.temp;
} else {
$scope.message = 'City not found';
}
}
function error(error) {
$scope.message = 'Server error'
}
return {
scope: {
destination: '=',
apiKey: '=',
onRemove: '&'
},
templateUrl: './destinationDirective.html',
controller: destinationController
};
});
I also tried:
$http.get(url).then(angular.bind(this, success, destination), error);
and:
$http.get(url).then(success.bind(this, destination), error);
But now I got the content of the destination and lose the response of the API
EDIT ABOUT SUPPOSED DUPLICATION: My problem is unique because the subject is about angular context when defined multiples functions on a controller, I find a solution and I will post it right now.
The trick here is to define a "private member attribute" for the "class" destinationController like below. The functions look back on the previous execution context and find the desired attribute. Not an evidence before I asked the question from my classic OOP background (Java, C# and PHP) when I had to reformat some callbacks hell like it was originally.
app.directive('destinationDirective', function () {
function destinationController($http, $rootScope, $scope) {
var _destination;
$scope.getWeather = function (destination) {
_destination = destination;
var url = 'http://api.openweathermap.org/data/2.5/weather?q=' + destination.city
+ '&appid=' + $scope.apiKey
+ '&units=metric';
$http.get(url).then(success, error);
};
function success(response) {
if (response.data.weather) {
_destination.weather = {};
_destination.weather.main = response.data.weather[0].main;
_destination.weather.temp = response.data.main.temp;
} else {
$rootScope.message = 'City not found';
}
}
function error(error) {
$rootScope.message = 'Server error'
}
}
return {
scope: {
destination: '=',
apiKey: '=',
onRemove: '&'
},
templateUrl: './destinationDirective.html',
controller: destinationController
};
});
I've tried to do this in many ways, ui-sref is not working for me ok because data make it undefined by the time it is created.
I'm quite new even programming but fighting a lot with angular these days.
The point is that is this possible to really create a fully custom $state.go?
When I say fully custom is, how can i construct even the key of the parameter?
In:
$state.go(stateName, {key:value}
Thanks
angular.module('app').directive('directive', ['$state', function ($state) {
function link(scope, elements, attibutes) {
//variable data coming example
data = {
name: 'name',
params: {
key: 'id',
value: 'sidvalue'
}
}
scope.back = function (data) {
$state.go(data.name, '{' + data.params.key + ':"' + data.params.value + '"}');
}
}
return {
restrict: 'E',
link: link,
templateUrl: 'path.html'
};
}]);
EDIT: *********
This is the historic factory gathering info from each state with push and getting it from with my logic when is convenient with get:
angular.module('app').factory('Historic', ['$q', '$state', function ($q, $state) {
var historic = [];
return {
historic: {
push: function (key, value) {
var str, init = 'general.home';
str = {
name: $state.current.name,
params: {
key: key,
value: value
}
};
if ($state.current.name === init) {
historic = [{
name: $state.current.name,
}];
} else if (historic.length <= 0) {
historic.push({name: init});
} else if (historic[historic.length - 1].name !== str.name && historic[historic.length - 1].params !== str.params) {
historic.push(str);
}
},
get: function () {
var h = historic[historic.length - 2];
if (historic.length === 1) {
h = historic[0];
}
return $q(function (resolve, reject) {
resolve(h);
});
},
pop: function () {
historic.pop();
},
status: function () {
return historic.length;
}
}
};
}]);
For getting it I'm using a directive with a bit more code attached.
Publishing only the related to historic part.
angular.module('app').directive('directiveName', ['$state', 'fHistoric', function ($state, fHistoric) {
function link(scope, elements, attibutes) {
/*
historic setup
*/
if (fHistoric.historic.status > 3) {
scope.home = true;
}
function keycomp(data) {
if (data.params) {
key = {id: data.params.value};
} else {
key = {};
}
}
scope.back = function () {
fHistoric.historic.get()
.then(function (data) {
keycomp(data);
if (key) {
$state.go(data.name, key);
} else {
$state.go(data.name);
}
});
fHistoric.historic.pop();
};
}
return {
restrict: 'E',
link: link,
scope: {
header: '#',
state: '#'
},
templateUrl: 'path.html'
};
}]);
I really don't like the solution proposed, it is working but the problem with the key values made me to wrap some spaguetti code I really don't like to solve the id: coming from the key value.
Open to new ideas. ;)
On load of page, I am making a restful call and getting data. This is working fine. On click of these data (which are left navigation links) I need to make another rest call and get data. I am trying to do that from link function. Since I am new to angular js, I am not sure how good/bad it is to call from there. I tried with several examples on how to call restful web service from link function but failed to implement successfully.
My js file code is as follows:
var app = angular.module('ngdemo', []);
app.directive('collection', function() {
return {
restrict: "E",
replace: true,
scope: {
collection: '=',
articleData: '=',
articleContent: '='
},
template: "<ul><member ng-repeat='member in collection' member='member' article-data='articleData' article-content='articleContent'></member></ul>"
}
});
app.directive('member', function($compile) {
return {
restrict: "A",
replace: true,
scope: {
member: '=',
articleData: '=',
articleContent: '='
},
template: "<div><li>{{member.title}}</li></div>",
link: function(scope, element, attrs) {
scope.getContent = function(itemId) {
//scope.articleContent.content = articleData[0].getArticleResponse.articleDetail.articleContent;
//scope.articleContent.title = articleData[0].getArticleResponse.articleDetail.title;
var request = $http({
method: "post",
url: "http://10.132.241.41:8082/apdpoc/services/ApdBookService/getArticle",
params: {
action: "post"
},
articleContents: {
getArticleCriteria:{
articleId: itemId,
locale: "en_US"
}
}
});
return( request.then(handleSuccess,handleError));
}
if (angular.isArray(scope.member.tocItem)) {
if (scope.member.hasChildren == "true") {
for (var i = 0; i < scope.member.tocItem.length; i++) {
if (scope.member.tocItem.title) {
scope.member.tocItem.title.hide = true;
}
}
}
element.append("<collection collection='member.tocItem'></collection>");
$compile(element.contents())(scope)
}
}
}
});
app.controller('apdController', function($scope, getTocService) {
var sampdata = getTocService.getToc('bookid-1');
$scope.tasks =sampdata;
$scope.articleContent = {};
});
app.service(
"getTocService",
function( $http, $q ) {
return({
getToc: getToc
});
function getToc(bookIdvar) {
var request = $http({
method: "post",
url: "http://10.132.241.41:8082/apdpoc/services/ApdBookService/getTOC",
params: {
action: "post"
},
data: {
getTOCCriteria:{
bookId: bookIdvar
}
}
});
return( request.then(handleSuccess,handleError));
}
}
);
function handleSuccess(response){
return (response.data);
}
function handleError( response ) {
if (
! angular.isObject(response.data) ||
! response.data.message
) {
return($q.reject("An unknown error occurred."));
}
return($q.reject(response.data.message));
}
on click of left nav links, I am not getting any error in browser console but its not hitting service . getContent() is the method I am trying to call on click of left nav links. Can someone please help me on this?
Edited:
Now, I am able to hit the rest service but getting "server responded with a status of 400 (Bad request)" . Request sent is
{
"getArticleCriteria" :{
"articleId": "PH1234",
"locale": "en_US"
}
}
This is the expected request and I am able to get response in Soap UI for the same request. Any changes I need to make while calling the rest service?
This is a cleaned up code with injection of $http service into directive
var app = angular.module('ngdemo', []);
app.directive('collection', function () {
return {
restrict: 'E',
replace: true,
scope: {
collection: '=',
articleData: '=',
articleContent: '='
},
template: '<ul><member ng-repeat="member in collection" member="member" article-data="articleData" article-content="articleContent"></member></ul>'
}
});
app.directive('member', function ($compile, $http) { //NOTE THE INJECTED $http
return {
restrict: 'A',
replace: true,
scope: {
member: '=',
articleData: '=',
articleContent: '='
},
template: '<div><li>{{member.title}}</li></div>',
link: function (scope, element, attrs) {
scope.getContent = function (itemId) {
//scope.articleContent.content = articleData[0].getArticleResponse.articleDetail.articleContent;
//scope.articleContent.title = articleData[0].getArticleResponse.articleDetail.title;
var request = $http({
method: 'post',
url: 'http://10.132.241.41:8082/apdpoc/services/ApdBookService/getArticle',
params: {
action: 'post'
},
articleContents: {
getArticleCriteria: {
articleId: itemId,
locale: 'en_US'
}
}
});
return ( request.then(handleSuccess, handleError));
};
if (angular.isArray(scope.member.tocItem)) {
if (scope.member.hasChildren == 'true') {
for (var i = 0; i < scope.member.tocItem.length; i++) {
if (scope.member.tocItem.title) {
scope.member.tocItem.title.hide = true;
}
}
}
element.append('<collection collection="member.tocItem"></collection>');
$compile(element.contents())(scope)
}
}
}
});
app.controller('apdController', function ($scope, getTocService) {
$scope.tasks = getTocService.getToc('bookid-1');
$scope.articleContent = {};
});
app.service('getTocService',
function ($http, $q) {
return ({
getToc: getToc
});
function getToc(bookIdvar) {
var request = $http({
method: 'post',
url: 'http://10.132.241.41:8082/apdpoc/services/ApdBookService/getTOC',
params: {
action: 'post'
},
data: {
getTOCCriteria: {
bookId: bookIdvar
}
}
});
return ( request.then(handleSuccess, handleError));
}
}
);
function handleSuccess(response) {
return (response.data);
}
function handleError(response) {
if (!angular.isObject(response.data) || !response.data.message) {
return ($q.reject('An unknown error occurred.'));
}
return ($q.reject(response.data.message));
}
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