This is my controller:
app.controller("PlaceController", ['$http', function($http){
this.places = shops;
var hotels = this;
hotels.objects = [];
this.spots = new Array;
this.newPlace = {};
this.city = new String();
this.addPlace = function() {
this.places.push(this.newPlace);
this.newPlace = {};
var request = *some query syntax, sorry for hiding*
$http.get(request).success(function(data) {
hotels.objects = data;
console.log(hotels.objects.elements);
});
for (each in hotels.objects.elements) {
this.spots.push(each.tags.name);
};
console.log(this.spots);
}}] );
I get an empty array when I log this.spots to the console. The http request etc work perfectly because the console.log(hotels.objects.elements) statement works perfectly.
Because of this problem, I can't output it into my HTML either. What should I do?
You are issuing an asynchronous request to get the spots, but you're logging them before they complete.
Change this.addPlace to log / act on the spots array inside the promise callback:
this.addPlace = function() {
this.places.push(this.newPlace);
this.newPlace = {};
var request = *some query syntax, sorry for hiding*
$http.get(request).success(function(data) {
hotels.objects = data;
console.log(hotels.objects.elements);
for (each in hotels.objects.elements) {
this.spots.push(each.tags.name);
};
console.log(this.spots);
});
You're adding to the spots array before the ajax request is done, move your calls to push inside the callback:
$http.get(request).success(function(data) {
hotels.objects = data;
console.log(hotels.objects.elements);
angular.forEach(hotels.objects.elements, function(value) {
hotels.spots.push(value.tags.name);
});
});
Also, you should really be using $scope instead of references to this. This would simplify your code a bit, without needing to rename this to hotels
full controller code using $scope
app.controller("PlaceController", ['$scope', '$http', function($scope, $http){
$scope.places = shops;
$scope.objects = [];
$scope.spots = new Array;
$scope.newPlace = {};
$scope.city = new String();
$scope.addPlace = function() {
$scope.places.push($scope.newPlace);
$scope.newPlace = {};
var request = *some query syntax, sorry for hiding*
$http.get(request).success(function(data) {
$scope.objects = data;
console.log($scope.objects.elements);
angular.forEach($scope.objects.elements, function(value, key) {
$scope.spots.push(value.tags.name);
});
// spots is ready, log it, do whatever
console.log($scope.spots);
});
}}] );
NOTE: Using $scope means you won't need to call this from your html to reference the objects and functions defined in your controller.
An example:
<div ng-controller="PlaceController">
<!-- no need to call places.city, if you use $scope just write city -->
{{city}}
</div>
EDIT: You probably shouldn't use JavaScript's for-in, the problem with it is that it iterates on the names or indexes of your objects/arrays.
An example:
var someArray = ['a', 'b', 'c'];
for (i in someArray) {
console.log(i); // prints 0, 1, 2
console.log(someArray[i]); // prints 'a', 'b', 'c'
}
This is different from any for-in/for-each implementation in other popular languages.
Anyway, in this case I've edited the code above to use Angular's forEach, which is a more appropriate solution (many libraries implement custom for-each functions to fix JS's weird for-in)
You can read more in Angular's docs
Another option, in plain javascript is, if $scope.objects.elements is an array, using the map() function, like this:
$scope.spots = $scope.objects.elements.map(function(value) {
return value.tags.name; // value is an item in object.elements
});
try this ..
due to your async call you need to perform task inside success
$http.get(request).success(function(data) {
hotels.objects = data;
console.log(hotels.objects.elements);
for (each in hotels.objects.elements) {
hotels.spots.push(each.tags.name);
};
console.log(this.spots);
});
Related
--- Given this code in a file ---
angular.module('storyCtrl', ['storyService'])
.controller('StoryController', function(Story, socketio) {
var vm = this;
Story.getStory()
.success(function(data) {
vm.stories = data;
});
vm.createStory = function() {
vm.message = '';
var newMessage = vm.storyData.content;
var newStory = { str: newMessage , timeNow: new Date(), mess: "Hello" };
Story.createStory(newStory)
.success(function(data) {
vm.storyData = '';
vm.message = data.message;
});
};
socketio.on('story', function(data) {
vm.stories.push(data);
})
})
Where does "data" being initialize or where is it coming from as it is not even a global variable or from ['storyService'].
Thank you
The variable data represents what the function (getStory or createStory or the on function) is returning to you for use in the function. For example, the getStory function might be returning a json array. Within the success function, this data is assigned to the vm.stories variable.
Does that help?
The storyCtrl module references another module called storyService. Slightly confusing, the storyService module contains a service (or factory) called Story. The Story service provides a method called getStory. Internally getStory very likely makes a call using $http. You can tell because getStory does not use standard promises, but instead uses the success and error methods that $http provides.
I am building a subscriber/observer pattern for displaying data in realtime for my angular app.
The observer is built with a factory injected into the angular controller and whose role is to fetch data and update it. The basic code structure can he found in this fiddle: http://jsfiddle.net/ctrager/67QR7/3/
var myApp = angular.module('myApp', [])
.factory('MyFactory', [function () {
var Collection = {};
Collection.isLoaded = 0;
Collection.data = [1, 2];
Collection.username = "corey and eric";
Collection.update = function () {
Collection.data.push(new Date())
}
Collection.replace = function () {
// If you do Collection.data = []
// here you are doing the same thing
// as the empty collection bug. I can't
// tell you EXACTLY why this confuses angular
// but I'm 99% sure it's the same phenomenon
Collection.data = [new Date()]
}
Collection.replace_fixed = function () {
// This works
Collection.data.length = 0
Collection.data.push(new Date())
}
return Collection;
}])
function MyCtrl($scope, MyFactory) {
$scope.name = 'Eric';
$scope.items = MyFactory.data;
$scope.replace = function(){
console.log("replace")
MyFactory.replace()
//$scope.items = MyFactor.data;
}
$scope.replace_fixed = function(){
console.log("replace_fixed")
MyFactory.replace_fixed()
//$scope.items = MyFactor.data;
}
$scope.update = function(){
console.log("update")
MyFactory.update()
}
}
The factory (MyFactory) contains a collection (Collection.data). Any push (/splice) to that collection is reflected in the scope, but if I replace the entire collection (Collection.replace()) the change is no longer reflected in $scope. Any idea why?
This works:
http://jsfiddle.net/67QR7/4/
changed the thing stored on scope to be the factory instead of data. then the html repeat to do items.data.
So it looks like this is because you replaced the reference inside collection, but that doesn't change where $scope.items was pointing to.
So you are creating a reference to MyFactory.data from $scope.items. Angular puts a $watch on $scope.items and looks for changes. When you call MyFactory.replace, you change MyFactory.data, but $scope.items remains the same. So as far as your watch is concerned, nothing has happened.
You can fix this by using replace_fixed, or watch for changes to MyFactory.data. http://jsfiddle.net/KtB93/
$scope.MyFactory = MyFactory;
$scope.$watch("MyFactory.data", function(newData) {
console.log('myFactory.data changed');
$scope.items = newData;
});
Or alternatively (probably better), you can use a function as the watch expression so you don't have to plop MyFactory on the scope (http://jsfiddle.net/XAW54/1/):
$scope.$watch(function() {
return MyFactory.data;
}, function(newData) {
$scope.items = newData;
});
This is the function that I am working with to call my factory
var myService = function($http) {
return {
bf: null,
initialize: function() {
this.promise = $http.get(this.server + "/requestkey").success(function(data) {
myService.bf = new Blowfish(data.key);
});
}
}
And I am creating this object using
TicTacTorrent.service('AService', ['$http', myService]);
However, when calling AService.initialize() it creates the promise object like it should, but it doesn't update the BF object. I'm confused as to how to update the bf object to be the new value. How would I reference myService.bf since this.bf would create a local instance for .success function?
Try this:
var myService = function($http) {
this.bf = null;
return {
bf: this.bf,
initialize: function() {
this.promise = $http.get(this.server + "/requestkey").success(function(data) {
myService.bf = new Blowfish(data.key);
});
}
}
Where do you want to initialize?
Have you seen the $provider example code?
Search for "provider(name, provider)" and check if it suits your need.
Otherwise I'm unsure what the code you'vew written will run like.
I usually write factories like this:
angular.module('app').factory('myService', ['$http', function($http) {
var publicObj = {};
publicObj.bf = ""; // Just to make sure its initialized correctly.
publicObj.initialize = function() {snip/snap... myService.bf = new Blowfish(data.key);};
return publicObj;
}]);
The difference might be that you previous code returned an inline anonymous object which might have a hard time referring to itself. But by that logic it should work by just making myService return a predeclared var and returning that.
I am having a really hard time deciphering what is going on here. I understand the basics of Angular's $digest cycle, and according to this SO post, I am doing things correctly by simply assigning a scoped var to a service's property (an array in this case). As you can see the only way I can get CtrlA's 'things' to update is by re-assigning it after I've updated my service's property with a reference to a new array.
Here is a fiddle which illustrates my issue:
http://jsfiddle.net/tehsuck/Mujun/
(function () {
angular.module('testApp', [])
.factory('TestService', function ($http) {
var service = {
things: [],
setThings: function (newThings) {
service.things = newThings;
}
};
return service;
})
.controller('CtrlA', function ($scope, $timeout, TestService) {
$scope.things = TestService.things;
$scope.$watch('things.length', function (n, o) {
if (n !== o) {
alert('Things have changed in CtrlA');
}
});
$timeout(function () {
TestService.setThings(['a', 'b', 'c']);
// Without the next line, CtrlA acts like CtrlB in that
// it's $scope.things doesn't receive an update
$scope.things = TestService.things;
}, 2000);
})
.controller('CtrlB', function ($scope, TestService) {
$scope.things = TestService.things;
$scope.$watch('things.length', function (n, o) {
if (n !== o) {
// never alerts
alert('Things have changed in CtrlB');
}
});
})
})();
There are two issues with your code:
Arrays don't have a count property; you should use length instead.
$scope.$watch('things.length', ...);
But there's a caveat: if you add and remove elements to/from the things array and end up with a different list with the same length then the watcher callback won't get triggered.
The setThings method of TestService replaces the reference to the things array with a new one, making TestService.things point to a new array in memory while both CtrlA.$scope.things and CtrlB.$scope.things remain pointing to the old array, which is empty. The following code illustrates that:
var a = [];
var b = a;
a = [1, 2, 3];
console.log(a); // prints [1, 2, 3];
console.log(b); // prints [];
So in order for you code to work you need to change the way TestService.setThings updates its things array. Here's a suggestion:
setThings: function (newThings) {
service.things.length = 0; // empties the array
newThings.forEach(function(thing) {
service.things.push(thing);
});
}
And here's a working version of your jsFiddle.
I don't really know why, but it seems to be corrected if you use a function to return the data in your service, and then you watch that function instead of the property. As it seems unclear, you can see it here : http://jsfiddle.net/DotDotDot/Mujun/10/
I added a getter in your service :
var service = {
things: [],
setThings: function (newThings) {
service.things = newThings;
},
getThings:function(){
return service.things;
}
};
then, I modified your code in both controller by this :
$scope.things = TestService.getThings();
$scope.getThings=function(){return TestService.getThings();};
$scope.$watch('getThings()', function (n, o) {
if (n !== o) {
// never alerts
alert('Things have changed in CtrlA');
}
}, true);
and in the HTML :
<li ng-repeat="thing in getThings()">{{thing}}</li>
It defines a function getThings, which will simply get the property in your service, then I watch this function (AFAIK $watch do an eval on the parameter, so you can watch functions), with a deep inspection ( the true parameter at the end). Same thing in your other controller. Then, when you modifies the value of your service, it is seen by the two $watchers, and the data is binded correctly
Actually, I don't know if it's the best method, but it seems to work with your example, so I think you can look in this way
Have fun :)
I am having a problem getting data from a service populated into my view. I have a service defined as such
app.factory('nukeService', function($rootScope, $http) {
var nukeService = {};
nukeService.nuke = {};
//Gets the list of nuclear weapons
nukeService.getNukes = function() {
$http.get('nukes/nukes.json')
.success(function(data) {
nukeService.nukes = data;
});
return nukeService.nukes;
};
return nukeService;
});
and my controller
function NavigationCtrl($scope, $http, nukeService){
/*$http.get('nukes/nukes.json').success(function(data) {
$scope.nukes = data;
});*/
$scope.nukes = nukeService.getNukes();
}
If I use the $http.get from the controller the data populates fine, however, if I try to call the data from the service, I get nothing. I understand that the query is asynchronous but I am having a hard time understanding how to populate the $scope variable once the data is returned. I could use $rootscope to broadcast an event and listen for it in the controller but this does not seem like the correct way to accomplish this. I would really appreciate any advice on how to do this the correct way.
I think this should solve your problem
app.factory('nukeService', function($rootScope, $http) {
var nukeService = {};
nukeService.data = {};
//Gets the list of nuclear weapons
nukeService.getNukes = function() {
$http.get('nukes/nukes.json')
.success(function(data) {
nukeService.data.nukes = data;
});
return nukeService.data;
};
return nukeService;
});
function NavigationCtrl($scope, $http, nukeService){
$scope.data = nukeService.getNukes();
//then refer to nukes list as `data.nukes`
}
This is a problem with object reference.
when you calls nukeService.getNukes() you are getting a reference to a object a then your variable $scope.nukes refers that memory location.
After the remote server call when you set nukeService.nukes = data; you are not changing the object a instead you are changing nukeService.nukes from referencing object a to object b. But your $scope.nukes does not know about this reassignment and it still points to object a.
My solution in this case is to pass a object a with property data and then only change the data property instead of changing reference to a
This should be as follows. As mentioned by NickWiggill's comment, undefined will be assigned to nukeService.data if we do not return promise.
app.factory('nukeService', function($rootScope, $http) {
var nukeService = {};
//Gets the list of nuclear weapons
nukeService.getNukes = function() {
return $http.get('nukes/nukes.json');
};
return nukeService;
});
function NavigationCtrl($scope, $http, nukeService){
nukeService.getNukes().then(function(response){
$scope.data = response.data;
});
}
What I do is that I expose the data straight from the service, and have a method which initializes this data. What is wrong with this?
Service:
app.factory('nukeService', function($scope, $http) {
var data = {};
data.nukes = [];
//Gets the list of nuclear weapons
var getNukes = function() {
$http.get('nukes/nukes.json').success(function(data) {
data.nukes = data;
});
};
// Fill the list with actual nukes, async why not.
getNukes();
return {
data : data
// expose more functions or data if you want
};
});
Controller:
function NavigationCtrl($scope, nukeService){
$scope.data = nukeService.data;
//then refer to nukes list as `$scope.data.nukes`
}