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.
Related
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.
first Stack Overflow post!
I'm trying to get my head around Angular, i've used jQuery for years, but im having requests in work to use it.
I'm loading in external JSON through a HTTP reqest..
$http({
method: 'GET',
url: "/Api/News/GetNews?pageNumber=" + pageNumber
}).then(function successCallback(response) {
$scope.myData = response.data
}, function errorCallback(response) {
showError()
});
}
This is working fine and the content is displaying in my NG-Repeat..
<div class="newsRow" ng-repeat="x in myData track by $index">
<div class="col-md-3">
Read more
</div>
<div class="col-md-9">
<h3>{{x.Title}}</h3>
<p>{{x.Excerpt}}/p>
{{x.NewsItemVersion}}
</div>
</div>
The only issue is the news ID (NewsItemVersion) needs to be formatted before it is displayed, it needs to be rounded to an integer.
How do I intercept this value and change it before it is displayed?
I would suggest to separate part of retrieving data and modifying. You can create 2 services for that and one controller to pass data to view.
e.g
Here is controller, it knows only about newsService and does not care about parsing id or whatever, just load.
app.controller('myController', ['$scope', 'newsService', function($scope, newsService) {
$scope.pageNumber = 1;
newsService.loadNews(pageNumber)
.then(function(news) {
$scope.news = news;
}, function(err) {
console.log(err);
})
}]);
Your service responsible for processing news and preparing them for controller or any other further usage. It knows what kind response will come and how to process it. In your case you can go through news list and call parseInt for all NewsItemVersion. So you have only one place where you modify it.
app.factory('newsService', ['$q', 'requestService', function($q, requestService) {
return {
loadNews: function(pageNumber) {
var defer = $q.defer();
requestService.getNews(pageNumber)
.then(function(data) {
data.forEach(function(item) {
item.NewsItemVersion = parseInt(item.NewsItemVersion, 10);
});
defer.resolve(data);
}, function(err) {
defer.reject(err);
});
return defer.promise;
}
}
}]);
And finally requestService or call whatever you want :) httpService, backendService etc. It is very thin layer which knows how and where to send request in order to get information from backend.
app.factory('requestService', ['$http', '$q', function($http, $q, requestService) {
var urls = {
'getNews': '/Api/News/GetNews?pageNumber=%pageNumber%'
};
return {
getNews: function(pageNumber) {
var requestUrl = urls['getNews'].replace('%pageNumber%', $pageNumber);
var defer = $q.defer()
$http({
method: 'GET',
url: requestUrl
}).then(function(response) {
defer.resolve(response.data);
}, function() {
defer.reject('Some meaningful message');
});
return defer.promise;
}
}
}]);
You have two main options here. Either you transform the data directly, as in the above answer of A Macdonald, or you use a filter.
If you're sure that you will only need the transformed data in your app, and you'll never need the original floating number, I'd suggest going with that first option. (I just noticed your comment on that answer, saying you can't seem to affect the $scope variable at all. If this is the case, I suggest you create a plunker or show us a bit more of your implementation, as this really should work.)
If you only need the rounded number for display purposes, and will still use the original floating number in you're logic, there are two approaches: You can create an additional property on the $scope called, for example, roundedNewsItemVersion. Or you can create a custom Angular filter. This is a really powerful feature of the language which you can use to transform any data, using a simple pipeline.
angular.module('yourApp', []).filter('absNumber', function() {
return function(input) {
// Optionally check wether input is a number, etc...
return Math.abs(input);
};
});
You can then use this filter in your code like this:
<div class="col-md-9">
<h3>{{x.Title}}</h3>
<p>{{x.Excerpt}}/p>
{{x.NewsItemVersion | absNumber}}
</div>
With custom filters, you can easily transform any data in your view, without actually changing the data itself. Of course, this filter is completely modular and you can continue to use it on different locations throughout your app. Now I know all this might be a bit of an overkill, but I hope it gives you some more insight into what exactly makes AngularJS a powerful framework.
By the way, I'd also suggest placing any asynchronous code that deals with getting data like your $http request in a service, instead of a controller. Controllers in Angular should only be used to add stuff to the $scope, not perform any meaningful logic.
handle it within the callback for the ajax call:
$http({
method: 'GET',
url: "/Api/News/GetNews?pageNumber=" + pageNumber
}).then(function successCallback(response) {
response.data.forEach(function (item) {
item.NewsItemVersion = Math.abs(item.NewsItemVersion);
});
$scope.myData = response.data
}, function errorCallback(response) {
showError()
});
}
Thanks everyone for the answers. I found the simplest answer was to use the forEach function as advised by A Macdonald, it essentially created a new data array with the manipulated content in it. I think this is the more jQuery type way of doing it, but it works for me.
$http({
method: 'GET',
url: "/Api/News/GetNews?pageNumber=" + pageNumber
}).then(function successCallback(response) {
var newsItems=[]
angular.forEach(response.data, function(value, key) {
newsItems.push({
Title : response.data[key].Title,
Excerpt : response.data[key].Excerpt,
Image : response.data[key].Image,
Url : response.data[key].Url,
NewsItemVersion : Math.abs(response.data[key].NewsItemVersion)
})
});
$scope.myData = newsItems;
}, function errorCallback(response) {
showError()
});
}
This method is useful as it allows the manipulation of the data before it is rendered out, so you could add variables, counters etc to the data before it is displayed.
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 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.
I have something like a master controller that sets some stuff in the scope, so the inner controllers can use it.
That setup work is asynchronous, so I've wrapped it in a promise, but it's not executing it's callback unless it was already resolved (I tried setting a breakpoint, and if I wait enough, it actually runs the then callback).
Here's a fiddle that reproduces my problem with a timeout rather than a network request: http://jsfiddle.net/LMv8v/1/
HTML
<div ng-app>
<div ng-controller="configController">
<div ng-controller="testC">
{{test}}
{{foo}}
</div>
</div>
</div>
Javascript
function configController ($scope, $q) {
var deferred = $q.defer();
$scope.config = deferred.promise;
setTimeout(function() {
console.log('timeout');
deferred.resolve({
'foo' : 'baz'
});
}, 1000);
};
function testC($scope) {
$scope.test = 'I am working, uh?';
$scope.config.then(function(config) {
console.log('then...');
$scope.$apply(function() {
$scope.foo = config.foo;
});
});
};
It shows the 'timeout', but not the 'then...' message.
(I know that this would be better suited for a Service, but I already have plenty of code with the nested scopes and I want to get it working before I start refactoring)
If you are using $.getJSON() (from jQuery I am guessing)
You will run into a similar issue where you are resolving something outside of the Angular world, try the following.
$.getJSON('ajax/test.json', function(data) {
$scope.$apply(function(){
deferred.resolve({
'foo' : 'baz'
});
});
});
Example on jsfiddle with jQuery ajax