I'm fairly new to angularJS but I've read that services should be singleton. However, it wont' work.
Here is my service:
.factory('TrainingPlan', function($http, SERVER){
var o = {
exercises: [],
planID : 0
};
o.init = function(){
if(o.exercises.length == 0)
return o.getAllExercises();
};
o.getAllExercises = function(){
$http({
method: 'GET',
url: SERVER.url + 'listener.php?request=getAllExercises&planID=' + o.planID
}).then(function(data){
o.exercises = angular.copy(o.exercises.concat(data.data));
console.log("Exercises in Trainingplan Services");
console.log(o.exercises);
return o.exercises;
})
;
};
o.getExercise = function(exerciseID){
for(var i = 0; i < o.exercises.length; i++){
if(o.exercises[i].exerciseID == exerciseID)
{
return o.exercises[i];
}
}
};
return o;
})
And I have two Controllers:
.controller('TrainingDetailCtrl', function($scope, $stateParams, TrainingPlan, $timeout) {
TrainingPlan.init();
$timeout(function(){
$scope.exercises = TrainingPlan.exercises;
$scope.numberOfUnfishedExercises = $scope.exercises.length;
button.innerHTML = "asdf";
}, 250);
})
(I haven't copied the whole controller, but it works so far...)
.controller('TrainingHistoryEditCtrl', function($scope, $stateParams, TrainingPlan, $timeout) {
var exerciseID = $stateParams.exerciseID;
$scope.currentExercise = TrainingPlan.getExercise(exerciseID);
console.log($scope.currentExercise);
})
So actually I go from TrainingDetailCtrl where I have all the exercises of 'TrainingPlan'. However, when I change the sites, TrainingPlan has no exercises anymore when I wont to use them in TrainingHistoryEditCtrl.
That is because your $http issues an async call. Even if you call init, actually when the code runs to the line $timeout(function(){.., the result may not arrive yet.
Please check this demo: JSFiddle. Wait for 10 seconds then the value is not empty.
Solution: return a promise from the factory. Inside the controller use then to pass in callback function.
Related
I'm just learning angularjs and I'm strugling to get the results I want from this factory :
app.factory('foreCast', ['$http', function($http,$scope ) {
var req = $http.jsonp("https://www.kimonolabs.com/api/c1ab8xnw?&apikey=api-key&callback=JSON_CALLBACK");
req.success(function (data) {
req.rows =data.results.collection1;
req.rand =req.rows[Math.floor(Math.random() * req.rows.length)];
console.log(req.rand);
//i want this results
});
return req; //how can i return req.rand?
}]);
app.factory('foreCast', ['$q', '$http', function ($q, $http) {
return {
getRand: function () {
var deferred = $q.defer();
$http
.jsonp("https://www.kimonolabs.com/api/c1ab8xnw?&apikey=api-key&callback=JSON_CALLBACK")
.then(function (response) {
var rows = response.data.results.collection1;
var rand = rows[Math.floor(Math.random() * rows.length)];
deferred.resolve(rand);
}, function (err) {
deferred.reject(err);
})
return deferred.promise;
}
}
}])
And then from controller for exemple use it like this :
foreCast.getRand().then(function(rand){
var myRand = rand;
})
So first of all, you never want to use the success handler with http. I believe its actually getting deprecated. The best practice is to use .then and .catch for errors. Secondly, most of the time you should be processing the result in your controller since the service is not supposed to know what to do with the data.
So that being said, we can trim down your factory a little bit.
app.factory('foreCast', ['$http', function($http,$scope ) {
var factory = {};
factory.rand = function() {
return $http.jsonp("https://www.kimonolabs.com/api/c1ab8xnw?&apikey=api-key&callback=JSON_CALLBACK");
};
return factory;
}]);
Now in your controller:
app.controller('controller', ['foreCast', function(foreCast) {
var self = this;
foreCast.rand().then(function(response) {
var rows = response.results.collection1;
self.rand = rows[Math.floor(Math.random() * rows.length)];
});
}]);
now in your view just access this value with rand.
<div ng-bind="ctrl.rand"></div>
I have the following controller
var app = angular.module('callapp', []);
app.controller('eController', function($scope, $http, $log) {
$scope.urlString = []; //this is filled with values
for( var i=0; i<3; ++i)
{
var currentURL = $scope.urlString[i];
$http.get(currentURL)
.success( function(response) {
//how do I access currentURL here?
$log.info(this.currURL) //this is printing "undefined"
});
}
The urls are generated dynamically, and I have to get data from these urls. The urls are generated before the for loop executes (and the url requests are asynchronous).
I tried $.ajax(currentURL) instead of $http.get method, but I got the same result - "undefined".
Is there any way I can access the currentURL and the current value of 'i' inside the .success(function ())?
currentUrl is easily accessible, and since you're making AJAX requests inside a for loop, you will always get i to be the last index value because the renderer will print the value when the AJAX req gives 200 which will take some time and within that time for loop would have executed, so always the last index value will be there in i. For this, you have to use IIFE
For the purpose of Demo, I'm using a Fake Online REST API - http://jsonplaceholder.typicode.com/
RUNNING DEMO: http://plnkr.co/edit/djPMu4UH9t9BeGwBcUMI
HTML:
<body ng-app="callapp" ng-controller="eController">
</body>
JS:
var app = angular.module('callapp', []);
app.controller('eController', function($scope, $http, $log) {
$scope.baseUrl = "http://jsonplaceholder.typicode.com/posts/";
$scope.urlString = [
$scope.baseUrl + '1',
$scope.baseUrl +'2',
$scope.baseUrl + '3'
]; //this is filled with values
for( var i=0; i<3; ++i) {
(function(index) {
var currentURL = $scope.urlString[i];
$http.get(currentURL)
.success( function(response) {
$log.info(index+1, currentURL);
});
})(i);
}
});
app.$inject = ['$scope', '$http', '$log'];
You can register an HttpInterceptor. In the interceptor it's possible to catch/handle all requests.
More info at:
https://docs.angularjs.org/api/ng/service/$http#interceptors
$provide.factory('myHttpInterceptor', function($q) {
return {
// optional method
'request': function(config) {
// This is your URL:
console.log(config.url);
return config;
},
};
});
Don't forget to register the httpInterceptor like this:
$httpProvider.interceptors.push('myHttpInterceptor');
store value in $scope variable because this.currUrl is undefined
var app = angular.module('callapp', []);
app.controller('eController', function($scope, $http, $log) {
$scope.urlString = []; //this is filled with values
for( var i=0; i<3; ++i)
{
$scope.currentURL = $scope.urlString[i];
$http.get($scope.currentURL)
.success( function(response) {
//how do I access currentURL here?
});
$log.info($scope.currentURL);
}
Instead of using for loop use angular forEach .For more information on angularForeach visit https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach?v=control
var app = angular.module('callapp', []);
app.controller('eController', function($scope, $http, $log) {
$scope.baseUrl = "http://jsonplaceholder.typicode.com/posts/";
$scope.urlString = [$scope.baseUrl + '1', $scope.baseUrl +'2',$scope.baseUrl + '3']; //this is filled with values
angular.forEach($scope.urlString,function(value,key){
$http.get(value)
.success( function(response) {
$log.info(value)
});
}
});
app.$inject = ['$scope', '$http', '$log'];
I'm trying to create a controller that gets data from Google app engine and allows me to display it on a page. The problem seems to be that the data (resp) can be accessed locally, but I can't seem to access it outside of the function. I am able to do so if I simply use javascript (...document.getElementById('getListingsResult').innerHTML = result;...), but if I invoke $scope for Angular, I can't access it any longer. Does anyone have any idea of how I can fix it while retaining the same structure to load and call gapi? Heres' my code:
(edit: added $scope.loadData, but problem persists)
phonecatControllers.controller('datastoreTestCtrl', ['$scope',
function($scope) {
$scope.data;
$scope.loadData = function() {
var ROOT = 'https://my_team.appspot.com/_ah/api';
gapi.client.load('listingserviceapi', 'v1', function(){
console.log("reached step 1");
var request = gapi.client.listingserviceapi.getListings();
request.execute(function (resp){
if (!resp.code) {
// console.debug(resp);
console.log('loaded! :)');//returns loaded
resp.items = resp.items || [];
$scope.data = resp.items;
console.log($scope.data); //returns an array of data
}
};
} , ROOT );};
$scope.loadData;
console.log($scope.data); //returns [object, object] which is incorrect
}]);
It should work using promise. Also, there is a missing parenthesis for request.execute function in your code.
Check the below code (untested):
phonecatControllers.controller('datastoreTestCtrl', ['$scope', '$q',
function ($scope, $q) {
$scope.data = null;
$scope.loadData = function () {
var deferred = $q.defer(),
ROOT = 'https://my_team.appspot.com/_ah/api';
gapi.client.load('listingserviceapi', 'v1', function () {
console.log("reached step 1");
var request = gapi.client.listingserviceapi.getListings();
request.execute(function (resp) {
if (!resp.code) {
// console.debug(resp);
console.log('loaded! :)'); //returns loaded
resp.items = resp.items || [];
//$scope.data = resp.items;
//console.log($scope.data); //returns an array of data
deferred.resolve(resp.items);
}
}); //---missing parenthesis here
}, ROOT);
return deferred.promise;
};
$scope.loadData().then(function (data) {
$scope.data = data;
console.log($scope.data); //returns [object, object] which is incorrect
});
}]);
That is because you are doing asynchronous call. When you trying to access $scope.data from outside of your callback your request is not finished yet it is still in process. You have to make sure that your request is done.
I'm really struggling with this because it should be very simple. I have a route with a controller defined called login. In my template I have the following data binding {{error}} which is defined in my controller after executing a method from a custom service, and resolving the returned promise.
Controller
app.controller("login", ['$scope','XMLMC', 'ManageSettings', function ($scope,api,ManageSettings) {
$scope.error = 'test';
$scope.login = function() {
var params = {
selfServiceInstance: "selfservice",
customerId: $scope.username,
password: $scope.password
};
var authenticated = api.request("session","selfServiceLogon",params).then(function(response) {
ManageSettings.set("session",response, $scope);
if(response.status === "ok") {
window.location.href = 'portal';
} else {
$scope.error = response["ERROR"];
console.log($scope.error);
}
});
};
}]);
The console shows Customer not registered. Showing that $scope.error has been updated appropriately, but the view never gets updated. My service is below, and please note that I am doing nothing "outside" of angular and so I should not have to $apply() anything manually.
app.factory("XMLMC", ['$http', '$q', function ($http, $q) {
function XMLMC($http, $q) {
$http.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
var that= this;
this.prepareForPost = function(pkg) {
return JSON.stringify(pkg);
};
this.request = function(service, request, params, host, newsession) {
var def = $q.defer();
var P = def.promise;
if(request === "analystLogon") {
newsession = true;
}
var call = {
service: service,
method: request,
params: params
};
if(host) {
call.host = host;
} else {
call.host = "localhost";
}
if(newsession) {
call.newsession = "true";
}
var pkg = {
contents: this.prepareForPost(call)
};
$http.post('php/XMLMC/api.php', jQuery.param(pkg)).success(function (response,status) {
that.consume(response, def);
}).error(function (response,status) {
def.reject(response,status);
});
return P;
};
this.consume = function(response, defer) {
console.log(response);
var resp = response[0],
digested = {},
i;
digested.status = resp["attrs"]["STATUS"];
var params = resp["children"][0]["children"];
for(i=0; i < params.length; i++) {
var key = params[i]["name"];
var val = params[i]["tagData"];
digested[key] = val;
}
defer.resolve(digested);
//return digested;
};
}
return new XMLMC($http, $q);
}]);
I've created a plunk here with the code exactly as it is on my test server. The routes and etc aren't working for obvious reasons, but you can at least see the code and how it works together
http://plnkr.co/edit/AodFJfCijsp2VWxWpbR8?p=preview
And here is a further simplified plunk where everything has one scope and one controller and no routes. For some reason, this works in the plunk but the $http method fails in my server
http://plnkr.co/edit/nU4drGtpwQwFoBYBfuw8?p=preview
EDIT
Even this fails to update
var authenticated = api.request("session","selfServiceLogon",params).then(function(response) {
ManageSettings.set("session",response, $scope);
$scope.error = "foo!";
if(response.status === "ok") {
window.location.href = 'portal';
}
});
It appears that $scope.$apply is indeed needed. See AngularJS - why is $apply required to properly resolve a $q promise?
To quote #pkozlowski.opensource:
In AngularJS the results of promise resolution are propagated asynchronously, inside a $digest cycle. So, callbacks registered with then() will only be called upon entering a $digest cycle.
This question is about half practical and half conceptual. I've looked at the responses to similar questions, but I'm pretty new to AngularJS, and I'm just not sure the best way (I've seen some varying opinions) to do this (for me, anyway), or really, the actual code I would write to do it, which is why I'm asking the question with the specifics of my own application.
I know there are lots of questions here with similar titles, but I urge you to keep reading.
In short, I have a bunch of controllers, because I have a lot of models I'm pulling together into a single page. When the front end dispatches a request (i.e., user action) to any of my back end controllers, the front end is going to get a response that may look something like this:
{"success":false,"errors":[{"text":"This is an error sent from the PHP controller.","type":"critical"}]}
I want to use AngularJS, however, to create the model and view for my error log (it only has to live on the client side). So in other words, every other controller in the application is going to need to have access to the error log controller in order to add events to the error log.
I guess I'm aware of some of the options, like creating a shared service/factory and broadcasting to the rootscope. I'm also wondering if it makes sense at all to make every other controller a child of the controller that handles errors, alerts, etc., though instinctively, that feels wrong to me.
What's the best way to do it (keeping in mind that the same controller that handles errors might also handle things like alerts and other global-type housekeeping), and would somebody be kind enough to help me out with the actual code based upon this model I mocked up for what the behavior would look like?
Here it is at JSFiddle: http://jsfiddle.net/Ww8sS/2/
And here's the code. There are probably a number of things in here that wouldn't be the best way to do something, but for now, I'm just concerned with the problem I've described.
JS:
var userInterfaceApp = angular.module('user-interface', ['userInterfaceFilters']);
userInterfaceApp.controller('AnotherController', ['$scope', '$http', function($scope, $http) {
$scope.doSomething = function() {
$http({method: "JSONP", url: "http://uatu.net/test.php?action=do_something&callback=JSON_CALLBACK"}).
success(function(data) {
if(!data.success) {
alert("How do I get the errors in data.errors to my error log?");
}
})
.error(function(data, status, headers, config) {
alert("Failure");
// called asynchronously if an error occurs
// or server returns response with an error status.
});
}
}]);
userInterfaceApp.controller('ConsoleEventController', ['$scope', function($scope) {
$scope.errorLog = [];
$scope.errorSortOrder = "-timestamp";
$scope.addToErrorLog = function(errorArray) {
for (var i = 0; i < errorArray.length; i++) {
$scope.errorLog.push({"text" : errorArray[i].text, "type" : errorArray[i].type, "timestamp" : new Date()});
}
}
//Not a real method--just here for demonstration
$scope.createErrorMessage = function() {
$scope.addToErrorLog([{"text" : "This is a sample error.", "type" : "critical"}]);
}
}]);
var userInterfaceFilters = angular.module("userInterfaceFilters", []);
userInterfaceFilters.filter("logTimestamp", function() {
return function(logDate) {
var hours = logDate.getHours();
var minutes = (logDate.getMinutes() < 10) ? "0" + logDate.getMinutes() : logDate.getMinutes();
var seconds = (logDate.getSeconds() < 10) ? "0" + logDate.getSeconds() : logDate.getSeconds();
return hours + ':' + minutes + ":" + seconds;
};
});
I had to use JSONP to make it work on JSFiddle. I wouldn't do that in my actual program, since it will all be on my server.
HTML:
<div ng-app="user-interface">
<div ng-controller="AnotherController">
<input type="button" value="Do Something" ng-click="doSomething()">
</div>
<div ng-controller="ConsoleEventController">
<p><input type="button" value="Create Error Message" ng-click="createErrorMessage()"></p>
<h1>Error Log</h1>
<ul id="error-log">
<li ng-repeat="error in errorLog | orderBy:errorSortOrder" class="error-{{error.type}}"><{{error.timestamp|logTimestamp}}> {{error.text}}</li>
</ul>
</div>
</div>
Sounds like you already know the best approach is to go down the factory/service route. There's no need to broadcast though - they just create a single instance which you can inject wherever necessary.
Here's a quick example of how you can go about it: http://jsfiddle.net/Ww8sS/3/
For me make more sense using a messaging paradigm (broadcast) rather than use factory that create a variable that after you bind to scope of the controller, because it does that the controller and the service/factory are coupled so you could never change the variable of the service/factory due that your controller would lose the link with the service/factory variable.
For example, if you would like to create a new method in the service/factory that clear the log's array so you create new array rather than empty the current array then that change wouldn't be reflected in that controller's scope because the scope's variable point to the old one log's array; take a look the example: http://jsfiddle.net/Ww8sS/6/
var userInterfaceApp = angular.module('user-interface', ['userInterfaceFilters']);
userInterfaceApp.factory('errorLogs', function () {
return {
logs: [],
addToErrorLog: function (errorArray) {
for (var i = 0; i < errorArray.length; i++) {
this.logs.push({"text": errorArray[i].text, "type": errorArray[i].type, "timestamp": new Date()});
}
},
clearLogs: function () {
this.logs = [];
}
}
});
userInterfaceApp.controller('AnotherController',
['$scope', '$http', 'errorLogs', function ($scope, $http, errorLogs) {
$scope.doSomething = function () {
$http({method: "JSONP", url: "http://uatu.net/test.php?action=do_something&callback=JSON_CALLBACK"}).
success(function (data) {
if (!data.success) {
errorLogs.addToErrorLog(data.errors);
}
})
.error(function (data, status, headers, config) {
alert("Failure");
// called asynchronously if an error occurs
// or server returns response with an error status.
});
}
}]);
userInterfaceApp.controller('ConsoleEventController',
['$scope', 'errorLogs', function ($scope, errorLogs) {
$scope.errorLog = errorLogs.logs;
$scope.errorSortOrder = "-timestamp";
//Not a real method--just here for demonstration
$scope.createErrorMessage = function () {
errorLogs.addToErrorLog([
{"text": "This is a sample error.", "type": "critical"}
]);
}
$scope.clearLogs = function () {
errorLogs.clearLogs();
};
}]);
var userInterfaceFilters = angular.module("userInterfaceFilters", []);
userInterfaceFilters.filter("logTimestamp", function () {
return function (logDate) {
var hours = logDate.getHours();
var minutes = (logDate.getMinutes() < 10) ? "0" + logDate.getMinutes() : logDate.getMinutes();
var seconds = (logDate.getSeconds() < 10) ? "0" + logDate.getSeconds() : logDate.getSeconds();
return hours + ':' + minutes + ":" + seconds;
};
});
If you use the messaging paradigm, it uncouple controllers with the service, moreover that the service is independent and any controller can listen its events; http://jsfiddle.net/Ww8sS/5/
var userInterfaceApp = angular.module('user-interface', ['userInterfaceServices', 'userInterfaceFilters']);
userInterfaceApp.controller('AnotherController', ['$scope', '$http', 'logger', function($scope, $http, logger) {
$scope.doSomething = function() {
$http({method: "JSONP", url: "http://uatu.net/test.php?action=do_something&callback=JSON_CALLBACK"}).
success(function(data) {
if(!data.success) {
logger.addToErrorLog(data.errors);
//alert("How do I get the errors in data.errors to my error log?");
}
})
.error(function(data, status, headers, config) {
alert("Failure");
// called asynchronously if an error occurs
// or server returns response with an error status.
});
}
$scope.clearLog = function() {
logger.clearLog();
}
}]);
userInterfaceApp.controller('ConsoleEventController', ['$scope', function($scope) {
$scope.errorSortOrder = "-timestamp";
$scope.errorLog;
$scope.$on('logger.newErrors', function (evt, errArray) {
$scope.errorLog = errArray;
});
$scope.$on('logger.clearLog', function (evt) {
$scope.errorLog = [];
});
//Not a real method--just here for demonstration
$scope.createErrorMessage = function() {
// $scope.addToErrorLog([{"text" : "This is a sample error.", "type" : "critical"}]);
}
}]);
var userInterfaceFilters = angular.module("userInterfaceFilters", []);
userInterfaceFilters.filter("logTimestamp", function() {
return function(logDate) {
var hours = logDate.getHours();
var minutes = (logDate.getMinutes() < 10) ? "0" + logDate.getMinutes() : logDate.getMinutes();
var seconds = (logDate.getSeconds() < 10) ? "0" + logDate.getSeconds() : logDate.getSeconds();
return hours + ':' + minutes + ":" + seconds;
};
});
var userInterfaceServices = angular.module('userInterfaceServices', []);
userInterfaceServices.service('logger', ['$rootScope', function ($rootScope) {
var errorLog = [];
this.addToErrorLog = function(errorArray) {
for (var i = 0; i < errorArray.length; i++) {
errorLog.push({"text" : errorArray[i].text, "type" : errorArray[i].type, "timestamp" : new Date()});
}
$rootScope.$broadcast('logger.newErrors', errorLog);
};
this.clearLog = function () {
errorLog = [];
$rootScope.$broadcast('logger.clearLog', '');
};
}]);
Anyway, both solutions have some advantages and drawbacks.