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
};
});
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'm trying to use geolocation data from one factory in angular in another factory that queries a weather API for data. I cannot figure out how to share the lat/lng variables between the two factories, I know you have use $scope but I cannot get the angular-promise to work correctly. Currently there are static lat/lng variables. The locate function in the controller doesn't even print to console, I think there's something wrong with my promise function.
Here are the factories:
'use strict';
app.config(['$resourceProvider', function ($resourceProvider) {
$resourceProvider.defaults.stripTrailingSlashes = false;
}]);
app.factory("geolocationService", ['$q', '$window', '$rootScope', function ($q, $window, $rootScope) {
return {
currentLocation: function() {var deferred = $q.defer();
if (!$window.navigator) {
$rootScope.$apply(function() {
deferred.reject(new Error("Geolocation is not supported"));
});
} else {
$window.navigator.geolocation.getCurrentPosition(function (position) {
$rootScope.$apply(function() {
deferred.resolve(position);
var geoJ = $scope.data;
//console.log(position);
});
}, function (error) {
$rootScope.$apply(function() {
deferred.reject(error);
});
});
}
return deferred.promise;
}
}
}]);
app.factory('weatherFactory', ['$http','geolocationService', function ($http, $scope, geolocationService) {
//var currentLocation = geolocationService.currentLocation();
var apiKey = 'd079ba76e47f06f2ea3483892f1b1d40';
var lat = '40',
lon = '-79';
return {
currentForecast: function(callback){
var url = ['https://api.forecast.io/forecast/', apiKey, '/', lat, ',', lon, '?callback=JSON_CALLBACK'].join('');
$http.jsonp(url)
.success(function (data){
callback(null, data);
//console.log("weatherFactory API Call: ");
//console.log(data);
})
.error(function (error) {
callback(error);
});
}
};
}]);
And here is the controller
app.controller('weatherCtrl', function ($scope, $window, weatherFactory, geolocationService) {
//console.log(geolocationService.currentLocation());
//var location = geolocationService.currentLocation();
//console.log("location object: ");
//console.log(location);
var locate = function(){
geolocationService.currentLocation().then(function(location){
$scope.location = location;
console.log($scope.location);
});
};
var pollForecast = function pollForecast() {
var commutes = calculateNextCommutes();
weatherFactory.currentForecast(function (err, data) {
if (err) {
$scope.forecastError = err;
} else {
$scope.forecast = data;
$scope.nextCommute = findCommuteWeather(commutes.nextCommute, $scope.forecast.hourly.data);
$scope.nextNextCommute = findCommuteWeather(commutes.nextNextCommute, $scope.forecast.hourly.data);
}
});
};
$scope.init = function () {
pollForecast();
setInterval(function () {
console.log('polling weather every hour')
pollForecast();
}, 1000 * 60 * 60); //poll every hour
}
});
I'm very new to Angular and would appreciate any help from the community, I'd like to be more active on here! Thanks in advance.
Best Regards,
-MC
First of all, DONT USE $SCOPE IN FACTORY, $scope is only available on controllers.
The solution for you problem is very simple. Have weatherFactory expose the lat/long by returning them:
return {
lat: lat,
lon: lon,
currentForecast: function(callback){
var url = ['https://api.forecast.io/forecast/', apiKey, '/', lat, ',', lon, '?callback=JSON_CALLBACK'].join('');
$http.jsonp(url)
.success(function (data){
callback(null, data);
//console.log("weatherFactory API Call: ");
//console.log(data);
})
.error(function (error) {
callback(error);
});
}
};
And then, inject weatherFactory into geolocationService and you'll be able to access lat/lon:
app.factory("geolocationService", ['$q', '$window', '$rootScope', 'weatherFactory', function ($q, $window, $rootScope , weatherFactory) {
return {
currentLocation: function() {var deferred = $q.defer();
var lat = weatherFactory.lat;
var lon = weatherFactory.lon;
if (!$window.navigator) {
$rootScope.$apply(function() {
deferred.reject(new Error("Geolocation is not supported"));
});
} else {
$window.navigator.geolocation.getCurrentPosition(function (position) {
$rootScope.$apply(function() {
deferred.resolve(position);
var geoJ = $scope.data;
//console.log(position);
});
}, function (error) {
$rootScope.$apply(function() {
deferred.reject(error);
});
});
}
return deferred.promise;
}
}
}]);
How about this:
app.factory("geolocationService", ['$q', '$window', '$rootScope', function ($q, $window, $rootScope) {
return {
currentLocation: function() {
var deferred = $q.defer();
if (!$window.navigator) {
$rootScope.$apply(function() {
deferred.reject(new Error("Geolocation is not supported"));
});
} else {
$window.navigator.geolocation.getCurrentPosition(function (position) {
$rootScope.$apply(function() {
deferred.resolve(position);
});
}, function (error) {
$rootScope.$apply(function() {
deferred.reject(error);
});
});
}
return deferred.promise;
}
}
}]);
app.service('weatherService', ['$http','geolocationService', function ($http, $scope, geolocationService) {
var apiKey = '...';
return function(callback){
//Note - a better, more angularish way to do this is to return the promise
//itself so you'll have more flexability in the controllers.
//You also don't need callback param because angular's $http.jsonp handles
//that for you
geolocationService.currentLocation().then(function(location){
var url = ['https://api.forecast.io/forecast/', apiKey, '/', location.lat, ',', location.lon, '?callback=JSON_CALLBACK'].join('');
return $http.jsonp(url)
.then(function(data){ callback(null,data); })
catch(callback);
}
};
}]);
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
});
I have this in the controller
angular.module('myApp')
.controller('TaskController', function ($scope, TaskFactory) {
$scope.addTodo = function () {
$scope.todos.push({text : $scope.formTodoText});
$scope.formTodoText = '';
};
});
and this in the factory
angular.module('myApp')
.factory('TaskFactory', function ($q, $http) {
var sendTasks = function(params) {
var defer = $q.defer();
console.log(1, params);
$http.post('http://localhost:3000/task/save', params)
.success(function(data) {
console.log(2);
console.log('data', data);
})
.error(function(err) {
defer.reject(err);
});
return defer.promise;
}
return {
sendTask: function(taskData) {
console.log('taskData', taskData);
return sendTasks('/task/save', {
taskData : taskData
})
}
}
});
all I need is to know, how to send the data from the controller to the factory in order to do the POST to the specified route ?
You just need to call the function/method inside factory with the required params.
angular.module('myApp')
.controller('TaskController', function ($scope, TaskFactory) {
$scope.addTodo = function () {
$scope.todos.push({text : $scope.formTodoText});
TaskFactory.sendTask({data : $scope.formTodoText})
$scope.formTodoText = '';
};
});
You can follow Dan Wahlin blog post.
Controller:
angular.module('customersApp')
.controller('customersController', ['$scope', 'dataFactory', function ($scope, dataFactory) {
$scope.status;
dataFactory.updateCustomer(cust)
.success(function () {
$scope.status = 'Updated Customer! Refreshing customer list.';
})
.error(function (error) {
$scope.status = 'Unable to update customer: ' + error.message;
});
}
Factory:
angular.module('customersApp')
.factory('dataFactory', ['$http', function($http) {
var urlBase = '/api/customers';
dataFactory.updateCustomer = function (cust) {
return $http.put(urlBase + '/' + cust.ID, cust)
};
}
Hope that solve your problem.
You can call the function directly on the TaskFactory that you pass into the controller as a dependency.
I've cleaned up your code a bit and created a plunk for you here:
And here's the code:
Controller
(function(angular) {
// Initialise our app
angular.module('myApp', [])
.controller('TaskController', function($scope, TaskFactory) {
// Initialise our variables
$scope.todos = [];
$scope.formTodoText = '';
$scope.addTodo = function() {
// Add an object to our array with a 'text' property
$scope.todos.push({
text: $scope.formTodoText
});
// Clear the input
$scope.formTodoText = '';
// Call function to send all tasks to our endpoint
$scope.sendTodos = function(){
TaskFactory.sendTasks($scope.todos);
}
};
});
})(angular);
Factory
(function(angular) {
angular.module('myApp')
.factory('TaskFactory', function($q, $http) {
var sendTasks = function(params) {
var defer = $q.defer();
$http.post('http://localhost:3000/task/save', params)
.success(function(data) {
console.log('data: ' + data);
})
.error(function(err) {
defer.reject(err);
});
return defer.promise;
}
return {
sendTasks: sendTasks
}
});
})(angular);
I want to pass a value from one element, to another html page.
Very simply- I want to click an object (within an ng-repeat), and be directly to a page with more detail about that object only.
Take a value (product_id) from the $http getUrl (a value not a directive in my html- can javascript still find it?). Pass this value so it can be accessed if "more info" is requested.
Using a value from take the current product_id, and use that number to fill a getURL to pull a json object for the following "detail" page.
Using ui-sref it opens a new page (not a new URL address, just a different HTML document)
In this new page it should have the product details
This is my my attempt:
.factory('cardsApi', ['$http', function ($http) {
var apiUrl = 'http://stashdapp-t51va1o0.cloudapp.net/api/item/';
function productId(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
var getApiData = function () {
return $http.get(apiUrl + productId(1, 50000))
};
var postRecordLikes = function (product_id) {
return $http.post('http://test.com/analytic/' + product_id);
}
return {
getApiData: getApiData,
postRecordLikes: postRecordLikes
};
}])
.controller('CardsCtrl', ['$scope', 'TDCardDelegate', 'cardsApi', '$http',
function ($scope, TDCardDelegate, cardsApi, $http) {
console.log('CARDS CTRL');
$scope.cards = [];
$scope.onSwipeRight = function (product_id) {
console.log(product_id)
}
$scope.onSwipeLeft = function (product_id) {
console.log(product_id)
}
// <====== Rewrite with accounts preferences
for (var i = 0; i < 7; i++) {
cardsApi.getApiData()
.then(function (result) {
//console.log(result.data) //Shows log of API incoming
$scope.cards.unshift(result.data);
$scope.product_id = result.data.product_id;
})
.catch(function (err) {
$log.error(err);
});
}
// Rewrite with accounts preferences =====>
$scope.$watchCollection('cards', function (newVal, oldVal) {
if (newVal < oldVal) {
cardsApi.getApiData()
.then(function (result) {
// console.log(JSON.stringify(result.data)); Shows log of API results
$scope.cards.unshift(result.data);
// console.log($scope.cards);
})
//.catch(function (err) {
// console.log(err);
//});
}
});
$scope.cardSwiped = function (card) {
console.log(card);
postRecordLikes(card);
};
//$scope.cards = Array.prototype.slice.call(cardTypes, 0);
//Removes card from top of stack
$scope.cardDestroyed = function (index) {
$scope.cards.splice(index, 1);
};
$scope.addCard = function () {
var newCard = $scope.cards[$scope.cards.length];
//newCard.id = Math.random();
$scope.cards.push(angular.extend({}, newCard));
};
var postRecordLikes = function (product_id) {
cardsApi.postRecordLikes(product_id)
.then(function successCallback(product_id) {
// this callback will be called asynchronously
// when the response is available
}, function errorCallback(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
};
}
])
.controller('ProductsCtrl', ['$scope', 'TDCardDelegate', 'productApi', '$http',
function ($scope, TDCardDelegate, cardsApi, $http) {
console.log('PRODUCTS CTRL');
}
])
.factory('productApi', ['$http',
function($http) {
var apiUrl = 'http://stashdapp-t51va1o0.cloudapp.net/api/item/' + product_id;
var getApiData = function() {
return $http.get(apiUrl)
};
return {
getApiData: getApiData
};
}]
)
My routing.js (trying to configure it to direct to any URL containing integers/numbers). This always redirects back to login...:
c
.config(['$stateProvider', '$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/login");
$stateProvider
.state('login', {
url: '/login',
views: {
'menuContent': {
templateUrl: 'app/core/login/login.html'
}
},
controller: function ($ionicHistory, $scope) {
console.log('Clearing history!');
$ionicHistory.nextViewOptions({
historyRoot: true,
disableBack: true
});
}
})
.state('product', {
url: '/product',
when:('/product/?:product_id'),
views: {
'menuContent': {
templateUrl: 'app/components/product/product.html'
}
}
})
a query string adds data to the end of a url, after a question mark. you then navigate to that url:
var myvar=234;
location="http://www.example.com/?"+myvar;
and in the second page get the variable by accessing the query string and stripping away the question mark:
var newvar=location.search.replace("?", ""); // 234
you would then use the 234 to make the specific ajax call, get the JSON, etc, etc