How can I access my data outside particular scope in Angular JS? - javascript

//declaring the module
var app = angular.module("sachin", ["ng-fusioncharts"]);
//declaring a factory
app.factory('team',function(){
return {
runs_aus : ''
};
});
app.controller("myCtrl", function($scope,$http,team){
$scope.australia=[];
$scope.total_runs_aus=0;
//fetching data from JSON
$http.get("convertcsv.json").then(function(response){
$scope.sachin_data=response.data;
angular.forEach($scope.sachin_data, function(value, key){
// manipulating data
if (value.opposition=="v Australia"){
if (value.batting_score=="-"|| value.batting_score == "TDNB" || value.batting_score == "DNB")
$scope.total_runs=$scope.total_runs;
else if (value.batting_score.substr(value.batting_score.length - 1) == "*"){
value.batting_score = value.batting_score.substr(1);
$scope.total_runs_aus+=parseInt(value.batting_score,10)
}
else
$scope.total_runs_aus+=parseInt(value.batting_score,10);
});
$scope.australia.push({ runs:$scope.total_runs_aus});
team.runs_aus=$scope.total_runs_aus;
//got final result in $scope.total_runs_aus
console.log(team.runs_aus);
//printing inside the scope(works fine)
});
console.log(team.runs_aus);
//printing outside the scope(can't access)
I am trying to access the total runs scored outside the
then(function()) of the get request
first I tried global variables in javascript
Now I tried using a factory Any help would be appreciated

You can use a service to store that data:
app.service('MyService', function() {
var self = {
'myString': 1,
'myObject': {},
'myArray': [],
'doSomething': function(param) {
self.myString = param
},
'anotherFunction': function() {
return true;
}
}
return self;
});
You just need to inject MyService on your controller and access it like MyService.myObject = something.

The important part to understand is that you are working async operations. The code continues to execute and prints your console logs even though the data has not been returned from $http.get(). Your code needs to account for this and run the code that operates on the data after its been resolved.
.then() expects a function as the parameter of the method signature. For example:
$http.get("convertcsv.json").then(function(response){
$scope.sachin_data=response.data;
}).then(function() {
console.log('Value in scope:', $scope.sachin_data);
});
or
function processData = function() {
console.log('Value in scope:', $scope.sachin_data);
};
$http.get("convertcsv.json").then(function(response){
$scope.sachin_data=response.data;
}).then(processData);
or chain multiple promises together (you must add angular's $q as a dependency):
function processData1 = function(data) {
//Create a deferred object.
var defer = $q.defer();
//Do something with data.
console.log('Value in scope:', data);
//Pass data to next promise in promise chain.
defer.resolve(data);
//Resolve data to be returned.
return defer.promise;
};
function processData2 = function(data) {
//Create a deferred object.
var defer = $q.defer();
//Do something else with data.
console.log('Value in scope:', data);
//Pass data to next promise in promise chain.
defer.resolve(data);
//Resolve data to be returned.
return defer.promise;
};
$http.get("convertcsv.json")
.then(processData1)
.then(processData2);
Please have a look at:
http://www.html5rocks.com/en/tutorials/es6/promises/
https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns
The code as is may have syntax errors as it has not been tested. Links are general reference and not specific to $q but the concepts are consistent.

Related

Http Service Factory Returns Undefined to Controller in Angular

I have this service, that initialize brands and actualBrand variables.
.factory('brandsFactory', ['$http', 'ENV', function brandsFactory($http, ENV){
var actualBrand = {};
var brands = getBrands(); //Also set the new actualBrand by default
function getBrands(){
var URL;
console.log('MOCKS enable? ' + ENV.mocksEnable);
if(ENV.mocksEnable){
URL = ENV.apiMock + ENV.getBrandsMock;
}else{
URL = ENV.apiURL + ENV.getBrands;
}
$http.get(URL).
success(function(data){
console.log('data is :' + data);
actualBrand = data[0];
console.log('Actual brand is ' + actualBrand);
return data;
}).
error(function(){
console.log('Error in BrandsController');
});
}
var setActualBrand = function(activeBrand) {
actualBrand = activeBrand;
};
var isSetAsActualBrand = function(checkBrand) {
return actualBrand === checkBrand;
};
return {
brands : brands,
actualBrand : actualBrand
};
And my controller looks like this:
/* BrandController to manage the html content of a single brand */
.controller('BrandCController', function(brandsFactory, $scope){
$scope.brands = brandsFactory.brands;
console.log('brands in controller is ' + $scope.brands + ' -- ' + brandsFactory.brands);
});
The problem in the controller is that it gets undefined in brandsFactory.brands because its loaded before the service.
Why can I do to solve this?
Im really new in angular and javascript, maybe Im doing lots of thing wrong.
Any help I would be grateful, thank you.
The reason why it is undefined is actually because of the asynchronous nature of $http requests. There is nothing (well, basically nothing) you can do to change this behaviour. You are going to have to resolve this using the most common way (and if you continue with Angular and most web development languages, this will become a long-standing practices) with promises.
What is a promise? There are many guidelines online for what it is. The most basic way to explain it to you is that a promise will not execute until the asynchronous operation returns.
https://docs.angularjs.org/api/ng/service/$q
http://andyshora.com/promises-angularjs-explained-as-cartoon.html
Right now, when you console.log the property, it may/may not have already been loaded (in fact, it probably hasn't) thus you get undefined.
Furthermore, I am not exactly sure about this, but you are using the factory provider, yet you don't return anything from the provider function, thus you are using it as if it were a service. I'm not 100% sure if Angular allows this, but certainly the best practice way is to return the instance of what you want to create from the factory.
Im really new in angular and javascript, maybe Im doing lots of thing wrong. Any help I would be grateful, thank you.
You are correct -- you are doing lot's of things wrong. Let's start with the getBrands function:
//Erroneously constructed function returns undefined
//
function getBrands(){
var URL;
console.log('MOCKS enable? ' + ENV.mocksEnable);
if(ENV.mocksEnable){
URL = ENV.apiMock + ENV.getBrandsMock;
}else{
URL = ENV.apiURL + ENV.getBrands;
}
$http.get(URL).
success(function(data){
console.log('data is :' + data);
actualBrand = data[0];
console.log('Actual brand is ' + actualBrand);
//SUCCESS METHOD IGNORES RETURN STATEMENTS
return data;
}).
error(function(){
console.log('Error in BrandsController');
});
The return data statement is nested inside a function which is the argument of an $http .success method. It does not return data to the getBrands function. Since there is no return statement in the getBrands body, the getBrands function returns undefined.
In addition the .success method of the $http service ignores return values. Thus the $http service will return a promise that resolves to a response object instead of a data object. To return a promise that resolves to a data object, use the .then method.
//Correctly constructed function that returns a promise
//that resolves to a data object
//
function getBrands(){
//return the promise
return (
$http.get(URL)
//Use .then method
.then (function onFulfilled(response){
//data is property of response object
var data = response.data;
console.log('data is :' + data);
var actualBrand = data[0];
console.log('Actual brand is ' + actualBrand);
//return data to create data promise
return data;
})
)
}
With a return statement inside the onFulfilled function and a return statement in the body of the getBrands function, the getBrands function will return a promise that resolves fulfilled with data (or resolves rejected with a response object.)
In the controller, resolve the promise with the .then and .catch methods.
brandsFactory.getBrands().then(function onFulfilled(data){
$scope.brand = data;
$scope.actualBrand = data[0];
}).catch( function onRejected(errorResponse){
console.log('Error in brands factory');
console.log('status: ', errorResponse.status);
});
Deprecation Notice1
The $http legacy promise methods .success and .error have been deprecated. Use the standard .then method instead.
UPDATE
I use $q promise to solve it. I use the .then in the controller, but I couldn't make it work in the factory. If you want to check it. And thanks for the feedback.
//Classic `$q.defer` Anti-Pattern
//
var getBrands = function(){
var defered = $q.defer();
var promise = defered.promise;
$http.get(URL)
.success(function(data){
defered.resolve(data);
})
.error(function(err){
console.log('Error in BrandsController');
defered.reject(err);
});
return promise;
};//End getBrands
This is a classic $q.defer Anti-Pattern. One of the reasons that the .sucesss and .error methods were deprecated was that they encourage this anti-pattern as a result of the fact that those methods ignore return values.
The major problem with this anti-pattern is that it breaks the $http promise chain and when error conditions aren't handled properly the promise hangs. The other problem is that often programmers fail to create a new $q.defer on subsequent invocations of the function.
//Same function avoiding $q.defer
//
var getBrands = function(){
//return derived promise
return (
$http.get(URL)
.then(function onFulfilled(response){
var data = response.data;
//return data for chaining
return data;
}).catch(function onRejected(errorResponse){
console.log('Error in BrandsController');
console.log('Status: ', errorResponse.status;
//throw value to chain rejection
throw errorResponse;
})
)
};//End getBrands
This example shows how to log a rejection and throw the errorResponse object down the chain to be used by other rejection handlers.
For more information on this, see Angular execution order with $q.
Also, Why is angular $http success/error being deprecated?.
And, Is this a “Deferred Antipattern”?
I think the issue you are having is that your controller is trying to access a property that is being set by an $http.get call. $http.get returns a promise, which is an asynchronous call to the URL you are providing to get your brands. That means that the controller making the call will not wait for the brands to be loaded.
There are a few options for solving this issue, but you can simply return the promise from the service and resolve it in the controller.
function getBrands(){
var URL;
console.log('MOCKS enable? ' + ENV.mocksEnable);
if(ENV.mocksEnable){
URL = ENV.apiMock + ENV.getBrandsMock;
}else{
URL = ENV.apiURL + ENV.getBrands;
}
return $http.get(URL);
});
Then in your controller you can put the success and error functions as so...
brandsFactory.brands().then(function(response){
$scope.brand = response.data;
$scope.actualBrand = response.data[0];
}, function(err){
console.log('Error in brands factory');
});
There are other options as well (such as lazy loading or putting the factory directly on your scope), but you can look into those if you'd like.
I solve it using $q promise.
.factory('brandsFactory', ['$http', '$q', 'ENV', function brandsFactory($http, $q, ENV){
var actualBrand = {};
var getBrands = function(){
var URL;
var defered = $q.defer();
var promise = defered.promise;
if(ENV.mocksEnable){
URL = ENV.apiMock + ENV.getBrandsMock;
}else{
URL = ENV.apiURL + ENV.getBrands;
}
$http.get(URL)
.success(function(data){
actualBrand = data[0];
defered.resolve(data);
})
.error(function(err){
console.log('Error in BrandsController');
defered.reject(err);
});
return promise;
};//End getBrands
var setActualBrand = function(activeBrand) {
actualBrand = activeBrand;
};
var isSetAsActualBrand = function(checkBrand) {
return actualBrand === checkBrand;
};
return {
setActualBrand : setActualBrand,
getBrands : getBrands
};
}])//End Factory
And in my controller:
/* BrandController to manage the html content of a single brand */
.controller('BrandCController', function(brandsFactory, $scope){
$scope.brands = [];
$scope.actualBrand = {};
$scope.setActualBrand = function(brand){
brandsFactory.setActualBrand(brand);
$scope.actualBrand = brand;
};
brandsFactory.getBrands()
.then(function(data){
$scope.brands = data;
$scope.actualBrand = data[0];
})
.catch(function(errorResponse){
console.log('Error in brands factory, status : ', errorResponse.status);
});
});//End controller
If I can improve my answer let me know. I use it all the information I got from the previous answers. Thank you anyone who helps.
UPDATE
Removing the $q.defer Anti-Pattern in my factory.
.factory('brandsFactory', ['$http', '$q', 'ENV', function brandsFactory($http, $q, ENV){
var actualBrand = {};
var getBrands = function(){
var URL;
if(ENV.mocksEnable){
URL = ENV.apiMock + ENV.getBrandsMock;
}else{
URL = ENV.apiURL + ENV.getBrands;
}
return (
$http.get(URL)
.then(function onFulfilled(response){
var data = response.data;
//return data for chaining
return data;
}).catch(function onRejected(errorResponse){
console.log('Error in BrandsController');
console.log('Status: ', errorResponse.status);
return errorResponse;
})
);
};//End getBrands
var setActualBrand = function(activeBrand) {
actualBrand = activeBrand;
};
return {
setActualBrand : setActualBrand,
getBrands : getBrands
};
}]);//End Factory

Angular Promise not working

I try to get some important things like: companyid,employeeid etc. with every request that a user makes. So this has to be received before everything else is done.
After that the user receives information based on his companyid that he sets with every request (get/company/{companyid}).
The problem that I have is that the response for receiving the companyid takes to long and angular already tries to make a request to (get/company/{companyid}) obviously there is no companyid yet.
I've tried to fix this whit promise but it's not working.
Here I try to receive some important information about the user(that I do with every request) :
Service
(function () {
angular.module('employeeApp')
.service('authenticationservice', authenticationservice);
function authenticationservice($http,$location,authenticationFactory,$q,GLOBALS,$cookies) {
this.validateUser = function () {
var vm = this;
vm.deferred = $q.defer();
data = {"api_token": api_token};
return $http.post(GLOBALS.url+'show/employee/' + $cookies.get('employeeid'),data)
.success(function(response)
{
vm.deferred.resolve(response);
})
.error(function(err,response)
{
vm.deferred.reject(err);
});
return vm.deferred.promise;
}
}
})();
Routes file
(In my routes file I use the authenticationservice to set all important users variables.)
employeeAppModule.run([
'authenticationservice',
'constants',
function(authenticationservice,constants) {
authenticationservice.validateUser()
.then(function(response)
{
constants.companyid = response.result.Employee;
constants.role = response.result.Role;
constants.name = response.result.FirstName;
console.log('test');
},
function(response){
console.log('error');
});
}
]);
So the problem is that the user information is set to late and angular already goes to my homeController where he uses the companyId that is not being set yet.
Thankyou
The problem in your current code is return $http.post are having two return statement in your validateUser method. Which is returning $http.get before returning return vm.deferred.promise; & that why customly created promise doesn't get returned from your method. Though by removing first return from $http.get will fix your problem, I'd not suggest to go for such fix, because it is considered as bad pattern to implement.
Rather I'd say, you should utilize promise return by $http method, & use .then to return data to chain promise mechanism.
Code
function authenticationservice($http, $location, authenticationFactory, $q, GLOBALS, $cookies) {
this.validateUser = function() {
var vm = this;
data = {
"api_token": api_token
};
return $http.post(GLOBALS.url + 'show/employee/' + $cookies.get('employeeid'), data)
.then(function(response) {
var data = response.data;
retrun data;
}, function(err) {
return $q.reject(err);
});
}
}
To make sure that $ http return a $ promise object you need to check that the action in the controller returns a value and it is not a void action.

Execute function after another AngularJS

I need to execute a function which fetches data after a kind of login function who provides the sessionId. This sessionId is necessary for the second function.
app.controller('TestController',
function ($scope, dbObjectsDAO, loginService){
var sessionID = loginService.getSessionID(); //Login function
var self = this;
this.items = [];
this.constructor = function() {
dbObjectsDAO.getAll(sessionID).then(function(arrObjItems){
$scope.items = arrObjItems;
});
};
this.constructor(); //get the data
return this;
});
I tried several variations like:
loginService.getSessionID().then(function(sessionID){
this.constructor(); //also with just constructor();
});
But I always receive errors (in the case above: Illegal constructor).
So how can I manage to execute one function after another ? Maybe a callback structure would help here but I have no clue how to realize it.
EDIT
Here is the code for the login:
app.service('loginService', function($http, $q) {
this.getSessionID = function()
{
return $http({
method: "GET",
url: "http://localhost:8080/someRequestDoneHere"
}).then(function(response)
{
return response.data.sessionId; // for example rYBmh53xbVIo0yE1qdtAwg
});
};
return this;
});
Does your getSessionID() function return a promise? If so you want code like this:
app.controller('TestController',
function ($scope, dbObjectsDAO, loginService){
var sessionID;
var vm = this;
vm.items = [];
loginService.getSessionID()
.then(function(sid) {
sessionID = sid;
return dbObjectsDAO.getAll(sessionID);
})
.then(function(arrObjItems){
vm.items = arrObjItems;
});
});
So your login service returns a promise which resolves to the session id. You can save that in a variable for use elsewhere, and also use it to trigger fetching the items.
I also changed your self to vm as that naming is an Angular convention, and stored the items in vm.items rather than directly in the scope.
Edit:
Your login code already returns a promise, not a session id. return inside a then is simply going to return a new promise that resolves to the value you are returning.
There are several ways to chain multiple $http requests. If they are independent of each other just fire off a bunch of requests and use $q.all to handle when they have all completed.
var promise1 = $http(something)
.then(function(response) { vm.data1 = response.data; return vm.data1; });
var promise2 = $http(something)
.then(function(response) { vm.data2 = response.data; return vm.data2; });
$q.all([promise1, promise2], function(values) {
// here can safely use vm.data1 or values[0] as equivalent
// and vm.data2 or values[1].
});
If one request depends on the result of another you could even do this:
var promise1 = $http(something)
.then(function(response) {
vm.data1 = response.data;
return { method:'GET', url: response.data.link}
});
var promise2 = promise1.then($http)
.then(function(response) { vm.data2 = response.data; return vm.data2; });
Your template needs to declare the controller using the 'controller as something' syntax:
<div ng-controller="TestController as test" ng-bind="test.items"></div>
Have you tried to nest the second function, like this ? without the constructor call ?
loginService.getSessionID().then(function(sessionID){
dbObjectsDAO.getAll(sessionID).then(function(arrObjItems){
$scope.items = arrObjItems;
});
});
Mb you have wrong scope in
..then(function(sessionID){...}) ?
you can try some this like this:
var vm=this;
loginService.getSessionID().then(function(sessionID){
vm.constructor();
});

How to handle information needed from a promise for future saves and updates

I have an object I'm saving to an API. The API returns a unique identifier: id.
When I save with I get a promise which includes my new, API assigned, id.
Now the user may want to carry out other operations, which will require the id. For example, renaming the widget and re-saving, or adding further objects that point back to its id.
What are the practical and ideally straightforward options*?
*Other advice on stackoverflow I've read suggests using 'resolve' which would be fine if I was reverting to the router at this point. But I'm not at the moment.
Here's a simple example:
widget.saveThis = function() {
if ('id' in this) {
this.put();
} else {
var _this = this;
rest.all('widgets').post(this).then(function(result) {
// Copy the new properties I have received to this object.
// ignore the methods from restangular.
for (var key in result) {
if (typeof(result[key]) != 'function')
_this[key] = result[key];
}
p.refresh();
});
}
};
Where if save is pushed twice in a row we might get two copies of the object.
Imagine you have a service where you do the API Communication (maybe via REST?
"use strict";
(function() {
var module = angular.module('myModule.service');
module.factory('myService', function($http, $q) {
return {
/**
* save and get
*/
saveAndGet: function(myObject) {
var deferred = $q.defer();
$http.post(getContextPath()+'/rs/saveObj', JSON.stringify{myObject})
.success( function(data) {
deferred.resolve(data);
})
.error(function(response) {
deferred.reject(response);
});
return deferred.promise;
}
}
});
})();
now imagine you have a controller where you wait for the saving to be done:
"use strict";
(function() {
var module = angular.module('myModule.controller');
module.controller('MyController' , function($scope, myService) {
var myObj = //set it somehow
$scope.id; //needed for save the "new" id
myService.saveAndGet(myObj)
.then( function(result) {
// is called if no error occured
$scope.id = result.id;
)};
})();
and then image you have that backend (in java for example)
#POST
#Path("saveObj")
#Produces({"application/json"})
public Response createProjectComment(MyObj obj) {
// do something and create myNewId
if (myNewId == null) {
return Response.ok("error").build();
}
return Response.ok(myNewId).build();
}
that would be one way to solve your problem.

AngularJS Promise Returns Empty Array

I have this code in a factory:
getAyahsByJuz: function (juzIndex) {
var response = [];
var promises = [];
var self = this;
var deferred = $q.defer();
$timeout(function () {
$http.get('data/quran.json').success(function (data) {
var ayahs = Quran.ayah.listFromJuz(juzIndex);
angular.forEach(ayahs, function (value, key) {
var promise = self.getVerse(value.surah, value.ayah).then(function (res) {
var verse = {
surah: value.surah,
ayah: value.ayah,
text: res
};
response.push(verse);
}, function (err) {
console.log(err);
});
promises.push(promise);
});
});
}, 30);
$q.all(promises).then(function() {
deferred.resolve(response);
});
return deferred.promise;
},
Please note that everything is working fine the verse object is returning properly. However, when I use this in a controller using .then(res). res returns [] instead of the array filled with the verse objects.
Can anyone point out why? Thanks!
The short answer is because your $q.all runs before $timeout & before the $http embedded in $timeout. Let's boil your original code down to its relevant components:
getAyahsByJuz: function (juzIndex) {
var response = [];
var promises = [];
var deferred = $q.defer();
// ...irrelevant stuff that will happen after a $timeout
// this happens IMMEDIATELY (before $timeout):
$q.all(promises).then(function() { // wait for empty promise array
deferred.resolve(response); // resolve with empty response array
}); // side note: this is a broken chain! deferred.promise can't reject
return deferred.promise; // send promise for empty array
}
See the problem? If for some odd reason you need to keep that $timeout, here's the fix with substantial promise refactoring & removing the awful jquery-inspired non-promisy success syntax):
getAyahsByJuz: function (juzIndex) {
var self = this;
// $timeout itself returns a promise which we can post-process using its callback return value
return $timeout(function () {
// returning the $http promise modifies the $timeout promise
return $http.get('data/quran.json').then(function (response) { // you never used this response!
var versePromises = [];
var ayahs = Quran.ayah.listFromJuz(juzIndex);
angular.forEach(ayahs, function (value, key) {
// we'll push all versePromises into an array…
var versePromise = self.getVerse(value.surah, value.ayah).then(function (res) {
// the return value of this `then` modifies `versePromise`
return {
surah: value.surah,
ayah: value.ayah,
text: res
};
});
versePromises.push(versePromise);
});
return $q.all(versePromises); // modifies $http promise — this is our ultimate promised value
// if a versePromise fails, $q.all will fail; add a `catch` when using getAyahsByJuz!
});
}, 30);
}
However, there is still a huge issue here… why aren't you using the server response of your $http call anywhere? What is the point of that first call?
Also I find that $timeout to be extremely suspicious. If you need it then it's likely there's something bad going on elsewhere in the code.

Categories

Resources