I'm coding a site for my friend's band that uses Angular as well as the SoundCloud API and I have not been able to get past this one problem.
In my first controller I was able to update the view with expressions populated by the soundcloud users JSON with a simple $http.get.
In my second controller, I wanted to grab each track + the track stats and put them in their own html paragraph with ng-repeat. However, when I do this, ng-repeat loops the appropriate amount of times (45X) yet only 3 of the created elements are populated with the track info. There are about 40 blank elements then the first three songs displayed as they should be followed by another blank section.
Here is my code:
(function(){
angular.module("ninety", [])
.controller("bandInfo", ['$http', function($http){
var ninetystuff = this;
ninetystuff.data = [];
$http.get('https://api.soundcloud.com/users/23749941.json?client_id=b4809581f93dc4d3308994300923b660').success(function(data){
ninetystuff.data = data;
});
}])
.controller("music", ['$http', '$scope', function($http, $scope){
var ninetyshit = this;
ninetyshit.data = [];
$scope.show = false;
SC.initialize({
client_id: "b4809581f93dc4d3308994300923b660"
});
SC.get('/users/23749941/tracks').then(function(tracks){
ninetyshit.data = tracks;
$scope.show = true;
});
$scope.playTrack = function(track) {
SC.oEmbed(track, {
auto_play: true,
maxheight: 200
}).then(function(embed){
$("#player").empty();
$("#player").append(embed.html);
});
};
}])
.directive('scrollToPlayer', function() {
return {
restrict: 'A',
link: function(scope, $elm, attr) {
$elm.on('click', function() {
$('html,body, #bg').animate({scrollTop: 400 }, 1000);
});
}
};
});
})();
I've tried creating a service to handle the promise returned from the 'GET' request but I had the same result.
I finally figured it out, I was parsing through the JSON data the wrong way. I should have provided my html code in the question. I was using ng-repeat through the data[0] array (ie - ng-repeat="index in data[0]") when I simply needed to loop through the returned data itself (ie- ng-repeat="index in data"). Silly mistake, I definitely over complicated things for myself. The fact that the wrong solution I was using displayed some but not all of the tracks made me falsely believe I was looping correctly when I wasn't.
Hope this helps any Angular noobs who are having similar issues. Lesson learned.
Related
I have some parameters in the $rootScope as specified below:
myApp.factory('itemService', function($http) {
return $http.get('/items');
});
myApp.run(function($rootScope, itemService) {
itemService.success(function(response) {
$rootScope.items = response;
});
});
myApp.controller('displayCtrl', function($rootScope, $scope) {
$scope.items = $rootScope.items;
});
When I run the above code, I get this error from firebug
TypeError: $rootScope.items is undefined. I really do not know what is happening.
Here is a small addition. items is an array with a list of objects like this:
items = [
{'name': 'spoon', 'price': 200},
{'name': 'table', 'price': 400},
{'name': 'shoe', 'price': 250}
];
I wish to make items available constantly in my app such that I can display each item on the item list (items) without making another request to the server. I intend to achieve this by simply displaying an item using $scope.item = items[$routeParams.id] each time I need to display an item.
I look forward to implement this using either a function attached to ng-click or the normal #/route/:param mechanism.
Thanks
TypeError: $object.property is undefined is usually because a request to a reference of an object is made before that specific object (or its property) has been set. $http requests are asynchroneous by nature so other processes do not get blocked. It should be obvious that trying to make requests synchroneous could cause a major issue for people with very slow connections.
Apart from that, polluting the $rootScope is generally a bad idea. You can find a topic about global variables on the following link so that you investigate why the $rootScope is not such a good place.
Having said all that, it seems to me that you didn't want to make multiple requests to retrieve the same data. If so, you can use the cache option for $http.get methods.
e.g:
myApp.factory('itemService', function($http, $q) {
return {
get: function() {
return $http({
url: 'items.json',
cache: true //keep the result in memory
});
}
};
})
myApp.controller('aCtrl', function(itemService) {
var self = this;
itemService.get().success(function(data) {
self.items = data;
});
});
myApp.controller('bCtrl', function(itemService) {
var self = this;
itemService.get().success(function(data) {
self.items = data;
});
});
This will make sure the information gets requested once and put into a cache. The data is accessible in different places.
<div ng-controller="aCtrl as a">
{{a.items}}
</div>
<div ng-controller="bCtrl as b">
{{b.items}}
</div>
This leaves me with another 'good' practice: the usage of the controllerAs syntax. Which provides a way to use namespaces in AngularJS.
Ofcourse, these are just tips and you should always consider the requirements!
You run asynchronious method at run block :
itemService.success(function(response){
$rootScope.items = response;
});
But initialization goes on, so probably you access $rootScope.items before itemService succeed (or it fails, and you didnt predict such situation). I suggest you to do this (if you want to follow $rootScope convension.. which is bad by the way) :
$rootScope.items = [];
itemService.success(function(response){
$rootScope.items = response;
});
You are setting items in the callback of an asynchronous process, so you are trying to access items on the $rootScope before its actually set.
If you are trying to initialize items when the controller is loaded, then there are other ways to do that such as using the resolve block of a route or manually calling the $http.get on the factory when the controller loads.
Finally, I was able to come up with a solution. I realized that the problem was to have $rootScope.items available in displayCtrl at the same time it loads. But $rootScope.items is available in my view when my html page loads.
So I simply passed the item id as a parameter and obtained it using $routeParams as follows
myApp.controller('displayCtrl', function($routeParams, $scope) {
$scope.item_id = $routeParams.id; //given that the route looks like '/item/:id'
});
Then in my HTML file this what I did
<div ng-bind="items[item_id].name"></div>
<div ng-bind="items[item_id].price"></div>
This actual solved my problem.
I've been with Angularjs a few days and I'm struggling with a few aspects of it. I'll do my best to try and explain what the issue is, and I'd really appreciate any help anyone can give me about it.
My situation (simplified) is this:
I have a service which loads some info from a json and stores it in an object. It also have some functions to be used for other controllers to retrieve that information.
var particServices = angular.module('particServices', []);
particServices.service('particSrv', function() {
var data = {};
this.updateData = function(scope) {
data = // http call, saves in data
}
this.getName = function(code) {
return data.name;
}
});
I have an html page assisted by a controller, which uses a directive board (no params, really simple). This is the controller:
var bControllers = angular.module('bControllers', []);
bControllers.controller('bController', ['$scope', 'particSrv', function ($scope, particSrv) {
$scope.getName = function(code) {
return particSrv.getName(code);
};
particSrv.updateData($scope);
}]);
As you can see, the controller makes the call to initialize the object in the service. As this is a singleton, I understand once that info is loaded no other call needs to be make to updateData and that info is available to others using the getters in the service (getName in this case).
I have a really simple directive board (which I simplified here), which uses another directive bio.
angular.module('tsDirectives', [])
.directive('board', ['dataSrv', 'particSrv', function(dataSrv, particSrv) {
return {
restrict: 'E',
replace: true,
scope: true,
controller: function($scope) {
$scope.getName = function(code) {
return particSrv.getName(code);
};
dataSrv.updateData($scope, 'board', 'U');
},
templateUrl: '<div class="board"><div bio class="name" partic="getName(code)"/></div></div>'
};
}]);
And this is the bio directive:
angular.module('gDirectives', [])
.directive('bio', function() {
return {
scope: {
partic: '&'
},
controller: function($scope) {
$scope.name = $scope.partic({code: $scope.athid});
},
template: '<a ng-href="PROFILE.html">{{name}}</a>'
};
})
Now, what I expected is that in the bio directive the info retrieved from party was displayed, but apparently this directive is processed before the partic is initialized in the main controller.
I was under the impression that even though this information was still not loaded when the directive is processed, as soon as the service finishes and the info is ready, automagically it would appear in my directive, but that does not seem to work like that. I've been reading about $watch and $digest, but I fail to see why (and if) I would need to call them manually to fix this.
Any hint will be much appreciated. I could provide more technical details if needed.
Directive will initialise when app is loaded and user opens the page where that directive is, if you have some property that is set later (from api for example), it will update that property in directive but that directive will not be reinitialised ($scope.partic({code: $scope.athid}) wont be called).
If you want for directive to wait for initialisation you should use ng-if. Something like this:
<div data-directive-name data-some-property="someProperty" data-ng-if="someProperty"></div>
In this case directive will be initialised when (if) you have some value in $scope.someProperty. But this is not very good if you can have false values for someProperty.
In that case you would need to use some kind of loaded flag.
You have not included "particServices" as a dependency in other modules which use the services of "particServices". Your modules should look like:
var bControllers = angular.module('bControllers', ['particServices']);
angular.module('tsDirectives', ['particServices']);
angular.module('gDirectives', ['particServices']);
I've spent the night on trying to figure this out and have finally decided to give up and ask for help.
I'm building a web-app with AngularJS that is designed to work with flakey connections (mobiles).
I'm trying to implement the functionality for a user to add a object (whether that's an appointment, book, etc is irrelevant) to the server.
Service that handles syncing objects with the server:
angular.module('App')
.service('syncUp', function syncUp($http, $q, app) {
this.addObject = function addObject(object) {
var deferred = $q.defer();
app.inSync = false;
var httpConfig = {
method: 'POST',
url: 'http://myurl.dev/app_dev.php/api/add-object',
data: object
}
function persist() { setTimeout(function() {
$http(httpConfig).
success(function(data, status) {
app.inSync = true;
deferred.resolve(data.id);
}).
error(function(data, status) {
app.inSync = false;
persist();
});
}, 3000);
};
persist();
return deferred.promise;
}
});
'app' service that the status bar is bound to:
'use strict';
angular.module('App')
.service('app', function app($http, $q) {
this.inSync = true;
});
Template binding to the 'app' service inSync property:
<div class="status" ng-class="{'insync':inSync}"></div>
Specific object service that sends data from the controller to the syncUp service:
this.addBook = function(book)
{
var tempId = syncUp.generateUid();
this.books[tempId] = book;
this.books[tempId].tempId = tempId;
syncUp.addObject({
'type': 'book',
'data': this.books[tempId]
}).then(function(newId) {
booksRef[newId] = book;
delete booksRef[tempId];
}, function() {});
}
Everything is working as it should (data is being persisted to the server and the ID is being returned and replacing the tempId just fine. The problem is, when the inSync key on the 'app' service is updated, the class isn't added/removed from the div as it should be with ng-class in the template. If I load another route, that will force iterate through whatever internal cycle angular is doing and update the class on the template.
I've tried all manner of $apply() solutions, moving where the app.inSync key is set back to true, looping a function watching it. It's being set in all the right places (from debugging I know it's set back to true correctly), I just can't figure out how to make the change appear on the UI.
I tried:
$rootScope.$apply(function() {
app.inSync = true;
});
Which gave me an error (already running a digest, or something).
So I tried the 'safeApply' version that has been circulated on many answers/blogs, which didn't throw the error, but didn't work either.
As far as I can figure out, the UI should be updated when promises are resolved (both the http and my syncUp.addObject promise are resolved, so I'm not sure why it's not working.
Any ideas? I need to keep the current implementation of promises to be able to set the returned ID from the server on the added object, to avoid a circular-dependency issue between the syncUp and object angular services.
Edit:
And the status bar directive:
angular.module('App')
.directive('navigation', function (app) {
return {
templateUrl: '/app/views/navigation.html',
restrict: 'E',
link: function (scope, element, attrs) {
scope.inSync = app.inSync;
}
}
});
References you make in templates refer to objects on the current $scope. Services do usually not create or add anything to the $scope, so putting properties on a service, will not make them available to the template. To get stuff on the $scope, you need to use a controller. You can use the ng-controller directive to reference a controller, you'll find examples of this in the first AngularJS tutorials.
What you should do is create a controller and have it listen for events from the service. Here's an example of how to do that.
That's the nice way; You might also be able to get away with it by putting the inSync = true on the $rootScope as such;
service('syncUp', function syncUp($http, $q, app, $rootScope) {
// (...)
$rootScope.inSync = true;
It looks like you're hoping to see bindings operating between a service ('app') and a template. It's hard to tell if we're not seeing the entire picture. Going on that assumption, you need to refactor so that you are setting up bindings on a controller.
I would expect the controller setup to look something like this:
angular.module('App')
.controller('app', function app($http, $q, $scope) {
$scope.inSync = true;
});
Now you will have two-way binding hooked-up on the 'inSync' property.
Otherwise, your template looks fine.
If I'm off base, please update your question with more context, or better yet make a fiddle to boil down the problem.
First off, i found the api address from this topic:
Laravel 4 and Angular JS and Twitter Bootstrap 3 Pagination
Now i am working about this, my little script is so:
var app = angular.module('kategori', [
'ngResource',
'apiBaseRoute'
]);
app.factory('Data', ['$resource', 'apiBaseRoute', function($resource, config){
return $resource('http://develop.alexei.me/careers/careers.php?callback=JSON_CALLBACK&page=:page', {
page: 1
}, {
'get': {
method: 'JSONP'
}
});
}]);
app.controller('KategoriListCtrl', function($scope, Data){
$scope.init = function() {
Data.get({}, function(response){
$scope.kategoriList = response.careers;
},function(error){
console.log("HATA VAR" + error);
});
};
});
app.directive('paginate', function(){
return{
scope:{ allData: '=paginate2' },
link: function(scope){
console.log(scope);
}
}
});
And this is the html side :
<div class="col-md-6 col-md-offset-3" ng-controller="KategoriListCtrl" ng-init="init()">
{{kategoriList}}
<div paginate paginate2="kategoriList"></div>
</div>
as you see, console.log(scope) inside directive is shows a lot of things in console, especially i see allData there with lots data, but if i change it to
console.log(scope.allData)
it prints undefined..
i don't understand why. how can i solve this? thanks.
By the time JS reaches your console.log the allData property is undefined (since kategoriList is undefined). kategoriList (and thus allData) is created (and populated with lots of data) asynchronously at a later time.
So, why do you see the data when logging the scope object instead ?
At the time the object is logged it has no property allData (and no data).
But by the time you go over to the console and expand the node and look for the allData property, the property has been added and populated by your AJAX call (using $resource).
It is not clear what you want to do with allData.
If you want to use it in e.g. ng-repeat you don't have to worry: You can use it normally (as if it were defined) and Angular will automatically "pick it up" as soon as it arrives and do stuff.
Yet, if you want (for your own mysterious reasons) to get informed when it is ready, your can use $watch:
scope.$watch('allData', function(newValue) {
if (newValue !== undefined) {
console.log(scope.allData);
}
});
See, also, this short demo.
On first load, I want an application to retrieve data asynchronously on first load from X number of http requests (X is based on the number of elements in fruits), and update a directive that shows how many items have been retrieved. When all items are retrieved, an event is triggered to cause that directive/dom element is hide itself.
What is the best way to accomplish this with angular? the following is what I think in terms of what the responsibilities of the service, controller, and directive are. Is this right? Or should there be a different/better way to do this?
APP.service('myService', function($http) {
this.fruits = ['Apple', 'Bannana', 'Pear', 'Orange'];
this.processedFruit = [];
});
APP.controller('myController', ['$scope', '$http', 'myService', function($scope,$http) {
$scope.$emit('LOAD');
// Should the following be in the service instead of the controller?
for(var i = 0; i < myService.fruits; i++) {
$http.get('someurl/'+fruits[i]).success(function(response) {
myService.processedFruit.push(response);
// Somehow tell the "statusofloading" directive to update its (Completed $http Requests)?
});
}
// Once all requests are finished $scope.$emit('UNLOAD') somehow to the directive?;
}]);
APP.directive('statusofloading', function() {
return {
scope:true,
restrict:'E',
link: function($scope,e,a) {
//Completed $http Requests
$scope.completeRequests = 0;
// Total $http Requests
$scope.totalRequests = // Get the number from the service somehow from (length of this.fruits);
$scope.$on('LOAD', function(){$scope.loading=true});
$scope.$on('UNLOAD', function(){$scope.loading=false});
},
replace: true,
template:"<h1>({{completedRequests}}) / ({{totalRequests}}) </h1>"
}
})
$emit takes a second parameter where you can pass whatever you want.
$scope.$emit('LOAD',{totalRequests: myService.fruits});
Then in your directive:
$scope.$on('LOAD', function(args){
$scope.loading=true;
$scope.totalRequests = args.totalRequests;
});
Then you tell the directive when a request is completed and it knows when all are done and it needs to hide itself. Make sure to notify the directive of failed requests as well as successful ones.
Also take a look at angular-busy for another more generic approach to the problem.