Why is $rootScope's property undefined? - javascript

I have an angular application that implements factory functions to handle some API requests for a global object that is implemented in almost all controllers.
factory.loadCart = function() {
var deferred;
deferred = $q.defer();
httpService.get({
service: 'cocacola',
param1: 'userCart',
guid: sessionStorage.token
}, function(r) {
if (r.error == 0) {
$rootScope.user.cart = r.result;
deferred.resolve(r.result);
} else {
deferred.reject("Error al cargar el carrito.")
}
}, function(errorResult) {
deferred.reject(errorResult);
});
return deferred.promise;
}
In the code I set the value of user.cart property as the result of the request. When I go to another controller that also implements this factory method (in this way)...
CartFactory.loadCart().then(function(response) {
$rootScope.user.cart = response;
$scope.cart = $rootScope.user.cart;
if ($rootScope.user.cart.productos.length == 0) {
$state.go('main.tienda');
} else {
getCards();
$rootScope.showCart = false;
}
}, function(error) {
$scope.loading = false;
$scope.showMe = false;
$state.go('main.tienda');
console.log(error);
});
... and go back to the first controller, the user.cart property is undefined and I can't proceed to execute the other functions that are defined as factory methods since the $rootScope.user.cart property is undefined and required as a parameter to these other functions. Also, the $rootScope.user.cart property gets its value after I refresh the browser (but I can't keep this as a solution), I'm very new to Angular so any help will be really appreciated, this is driving me nuts!

I've always found that $rootScope was somewhat difficult to work with, and something of an antipattern in AngularJS... it's like putting variables on the global scope in vanilla JS.
Is there any reason you wouldn't avoid the whole $rootScope.user.cart issue by just keeping cart in CartFactory and then putting an API in CartFactory to getCart, then return whatever cart is to any interested controller?

where you setting 'user' property on $rootScope? can you please put the whole code and always before reading the nested properties from object, check for undefined, in your case put if($rootScope.user) { // your logic }

Related

problems with angular factory

I'm using angular factory to share data between controller each controller is for one page. Here is my js file
app.factory('myService', function() {
var savedData = {};
function set(data) {
savedData = data;
}
function get() {
return savedData;
}
return {
set: set,
get: get
}
});
app.controller("logincont", ['$scope','$http','md5','$window','myService',function($scope,$http,md5,$window,myService){
$scope.cari = function () {
$http.get('http://localhost:8089/MonitoringAPI/webresources/login?a='+$scope.userid+'&d='+$scope.password).then(function(response){
$scope.reslogin = response.data;
$scope.reslogin2 = response.data.username;
myService.set($scope.reslogin2);
console.log($scope.reslogin2);
console.log(myService.set($scope.reslogin2));
});
};
}]);
app.controller("moncont", ['$scope','$http','$filter','myService',function($scope,$http,$filter,myService){
$scope.user = myService.get();
console.log($scope.user);
}]);
Here is the result when I call console.log
console.log($scope.reslogin2) = ristian
console.log(myService.set($scope.reslogin2)) = undefined
console.log($scope.user)={}
The result that I expected, ristian is filled each scope.
There are a lot of issues here, some have already been addressed in the comments. Another of the problems is that your set() function overrides the savedData object reference. So when you call myService.get() you get the reference to the empty object, then when the http request resolved and set is called, whatever you've assigned originally, still references the empty object.
So in the above example, this is what happens chronologically (assuming you call $scope.cari() at some point in time):
$scope.user = myService.get(); assigns a reference to the empty object in savedData to $scope.user
At some point you make a http request, that calls myService.set($scope.reslogin2);
This call to set overrides the reference in savedData.
$scope.user still references the old empty object.
To fix this particular issue, you need to either rethink your entire flow, or replace your set method with somethink like this
function set(data) {
angular.extend(savedData, data);
}
which mutates the savedData object, instead of overrides it. This can cause other issues, with empty properties not being overriden, but that entirely depends on what properties are in data.

Angular services with default values for non-existing attributes

Working on an Ionic application that performs both in Android and Windows.
There are services, such as Ionic's $ionicLoading, which we override functionality in order to work properly in windows:
angular.factory('$ionicLoading', function(){
return {
show: function (){...} // custom implementation
hide: function (){...} // custom implementation
}
});
But there are other services which we have to override only to not break the app.
In this cases it would be really useful to provide a service that won't do anything. For example:
angular.factory('$ionicExampleService', function(){
return {
*foo*: angular.noop // for operations
*bar*: promise // returns promise
}
});
Note: I know that a better way of doing this would be with a service that chooses between Ionic's implementation or a made one, but this is just for the sake of learning.
The ideal would be going even further, it would be magnificent to be able to return something even more bulletproof. Something like a generic flexible services:
angular.factory('$ionicPopup', function(){
return /*magic*/;
});
$ionicPopup.show({...}) // show was not defined
.then(foo); // won't break and will execute foo()
It is possible?
From what I understood you need to override implementation of existing services. You can do that with an angular service decorator.
A service decorator intercepts the creation of a service, allowing it to override or modify the behaviour of the service. The object returned by the decorator may be the original service, or a new service object which replaces or wraps and delegates to the original service.
For more information you can check angular documentation. One simple example would be:
app.factory('someService', function () {
return {
method1: function () { return '1'; }
method2: function () { return '2'; }
};
});
app.decorator('someService', function ($delegate) {
// NOTE: $delegate is the original service
// override method2
$delegate.method2 = function () { return '^2'; };
// add new method
$delegate.method3 = function () { return '3'; };
return $delegate;
});
// usage
app.controller('SomeController', function(someService) {
console.log(someService.method1());
console.log(someService.method2());
console.log(someService.method3());
});
EDIT: Question - How to override every method in the service?
var dummyMethod = angular.noop;
for(var prop in $delegate) {
if (angular.isFunction($delegate[prop])) {
$delegate[prop] = dummyMethod;
}
}
I hope that this helps you.
Using an evaluation for each assignment based on an object property, similar to this:
myVar = myObj.myPropVar === undefined ? "default replacement" : myObj.myPropVar;
Basically you're using a check for if the property has been defined, substituting a default value if it hasn't, and assigning it if it has.
Alternatively, you can use a modified version of the global function in Sunny's linkback to define defaults for all those properties you might assume to be undefined at specific points in your code.
function getProperty(o, prop) {
if (o[prop] !== undefined) return o[prop];
else if(prop == "foo") return "default value for foo";
else if(prop == "bar") return "default value for bar";
/* etc */
else return "default for missing prop";
}
Hope that helps,
C§
use var a = {}; to declare new variable.

$resource angular returning undefined with $promise

I have been stuck on this for quite a while now and cannot figure out why the value is not being returned. I am using Angular $resource to make a GET request to an API.
My $resource factory looks like this:
.factory("Bookings", function ($resource) {
return $resource("www.example/bookings_on_date/:day", {});
})
I have tried to implement promises but am unable to do so correctly.
function getBookings(day){
return Bookings.get({"day": day}).$promise.then(function(data) {
console.log(data.total)
return data.total;
});
}
$scope.todaysBookings = getBookings(TODAY);
$scope.tomorrowsBookings = getBookings(TOMORROW);
When I view either console.log($scope.todaysBookings) or $scope.tomorrowBookings in the console it returns undefined.
I have also tried everything from this jsfiddle but unfortunately have not had any luck.
I think it should be like this:
function getBookings(day) {
return Bookings.get({"day": day}).$promise.then(function(data) {
return data.total;
});
}
getBookings(TODAY).then(function(total) {
$scope.todaysBookings = total;
});
getBookings(TOMORROW).then(function(total) {
$scope.tomorrowsBookings = total;
});
Update: I think next code style could help you to prevent next extending method problems:
function getBookings(args) {
return Bookings.get(args).$promise;
}
getBookings({"day": TODAY}).then(function(data) {
$scope.todaysBookings = data.total;
});
getBookings({"day": TOMORROW}).then(function(data) {
$scope.tomorrowsBookings = data.total;
});
A little advantages here:
Pass object into function could help you easily pass different
arguments into method and arguments are very close to method call (a
little bit easy to read);
Return complete response from function
could help you to process different data (method could replay
different response depends on arguments, but it's not good practise
in this case);
p.s. Otherwise, you could remove function declaration and code like this (to keep it as simple, as possible):
Bookings.get({"day": TODAY}).$promise.then(function(data) {
$scope.todaysBookings = data.total;
});
Bookings.get({"day": TOMORROW}).$promise.then(function(data) {
$scope.tomorrowsBookings = data.total;
});

How to pass arguments to promise in AngularJS

I've go a small angular app with directive.
For retriving data from serverside I use ngRoute. After retriving data I bind result to a scope property and parse the result with ng-repeat like so:
<div class="col-xs-12" ng-repeat="clsRow in classificatorData">
<span>{{clsRow.code}}</span>
</div>
This function that retrievs data from resource
var getClassificatorDataScope = function (criteria, predicate) {
references.initialize('classificators');
references
.getRefereces(null, $scope.classificatorType, criteria, predicate == null ? "NONE" : predicate, $scope.limitLevel, null)
.$promise.then(function (result) {
$scope.classificatorData = result.Data;
});
};
Everything works fine. But if I try to implement passing result data container (dataScope) like so
var getClassificatorDataScope = function (criteria, predicate, dataScope) {
references.initialize('classificators');
references
.getRefereces(null, $scope.classificatorType, criteria, predicate == null ? "NONE" : predicate, $scope.limitLevel, null)
.$promise.then(function (result) {
dataScope = result.Data;
});
};
And use it in controller like so
getClassificatorDataScope("CODE", null, $scope.classificatorData);
I've got no data at all. Please help me to understand such behaviour.
There's 2 problems here.
dataScope = result.Data;
The first one is this. This doesn't act like how you'd expect it would. It doesn't replace the $scope.classificatorData. All it does is replace the local dataScope variable in getClassificatorDataScope to result.Data (yes, it's not "passed by reference").
Second, you're using promises incorrectly. You return the promise for listening, not pass the scope to who-knows-where. Your data layer should not be aware of $scope or the UI in general. Return the promise to the controller, and have it listen for the data from it.
// In your function
var getClassificatorDataScope = function(criteria, predicate) {
references.initialize('classificators');
return references
.getRefereces(null, $scope.classificatorType, criteria, predicate == null ? "NONE" : predicate, $scope.limitLevel, null)
.$promise
};
// In your controller
getClassificatorDataScope("CODE", null).then(function(result){
$scope.classificatorData = result.Data;
});
Seems that in second example you're trying to assign data retrieved from server to dataScope but since AJAX data loading is asynchronoys so $promise is resolved later than your template with ng-repeat is drawn.
There's not enough code provided in question to write whole example - how it should be implemented. But basically you should return $promise from your service and in controller change $scope variables upon
promise.then(function() {
//do stuff with $scope variables
})
The problem might be in your references.getRefereces method. It should return a promise and later resolve it with the proper result(I see you try to access "Data" attribute from result.). something like this:
reference.getReferences = function() {
var deferred = $q.defer();
someAsyncOperations(function callback(){
deferred.resolve({Data: result}) // make sure you have the "Data" attr
})
return deferred.promise;
// or if someAyncOperations already return a promise
// return someAsyncOperations.then(function(result) {
// return {Data: result};
// });
}

$http promise in angular service

I am having a problem with promises in an angular service. I have a service with a method getArea which is supposed to return the name of a service-area. The service gets the service-areas from the API. When getArea gets the service-areas, it finds the name of the requested area, and should return it. However, my code does not work - I get into an infinite loop. I guess I have misunderstood how to use promises?
SupplierService:
var servicePromise;
var getServices = function(){
if( !servicePromise ){
servicePromise = $http.get('/api/services')
.then(function(res){
return res.data.data;
});
}
return servicePromise;
};
var myService = {
getServices : getServices,
getArea : function(questionnaireId){
getServices().then(function(services){
// ...
return "hello world";
});
}
};
return myService;
Controller:
$scope.supplierService = SupplierService;
View:
<div>
<b>Area:</b> {{ supplierService.getArea(r.questionnaireId) }}
</div
I expect the view to show "Area: hello world", but gets into an infinite loop.
Update 1: I have added getServices as a public function in the service, and can access it from the controller like this:
SupplierService.getServices().then(function(d){
$scope.services = d;
});
Therefore I guess the problem is in the getArea method?
Update 2: I was inspired by this answer https://stackoverflow.com/a/12513509/685352. I want to cache the result.
Update 3: Here is a plunker. If you try accessing supplierService.getArea(100) from the view - the browser will not respond.
Your service should look more like this:
var getServices = function(){
var deferred = $q.deferred();
$http.get('/api/services')
.then(function(res){
deferred.resolve(res.data)
});
return deferred.promise;
};
Notice when you create a deferred you must return the deferred.promise (the actual promise) and then when you're async call returns you must call deferred.resolve or deferred.rejected as appropriate (to trigger the success or error functions respectively)
Minor addition I have a plunkr showing a few ways of getting data from a service into your controllers since this is a common issue for devs coming into Angular
http://plnkr.co/edit/ABQsAxz1bNi34ehmPRsF?p=info
It's not absolute best practices since I tried to keep it as simple as possible, but basically showing three different ways to "share" your data keep in mind some of these methods rely on angular.copy which means the property of the service you store the data on must be an Object or an Array (primitive types won't work since the reference can't be shared).
Here's a rewrite including the function inline:
var myService = {
var dataLoaded = false;
var data = {}; //or = [];
getServices : function(){
var deferred = $q.defer();
if( !dataLoaded ){
$http.get('/api/services').then(function(res){
angular.copy(res.data, myService.data);
deferred.resolve(myService.data);
}, function(err){
deferred.reject("Something bad happened in the request");
});
}
else
{
deferred.resolve(myService.data);
}
return deferred.promise;
}
};
return myService;
To explain, I create a new promise using the $q service which you'll need to inject to the service function. This allows me to either resolve that promise with data I already have or to make the call to the service and resolve that data but in both cases when this is being used it's assumed you will just get a promise back and are therefore dealing with an async operation. If you have multiple data sets to load you can use an object to store the flags instead of a single boolean.
i think if you return the $http callback?
//$http.get('/someUrl').success(successCallback);
var getServices = function(){
return $http.get('/api/services');
};
getServices.success(function(services){
// ...
return "hello world";
});
}

Categories

Resources