I am new to Angular, but managed to make an Ajax-call and print out users from Random User Generator API in a list view.
Now I want to make a detailed view while clicked on a user.
In my HTML I make a function call: fetchInfoById(user.id.value)
In my script the function:
$scope.fetchInfoById = function(info_id) {
$http.get("https://randomuser.me/api/?id.value="+info_id)
//also tried: $http.get("https://randomuser.me/api/?id/value="+info_id)
.success(function(data) {
$scope.oneUserResult = data.results;
});
}
It does give me a user to a detail view, but not the chosen one. What am I doing wrong?
Thanks for your good suggestions.
I know it is a random generator, but setting parameters for the request to: "seed=...", the same persons is displayed on each listview request:
$http.get('https://randomuser.me/api/?results=15&seed=abc&inc=gender,name,location,email,dob,phone,cell,id,picture,info,nat&nat=gb')
.success(function(response){
$scope.userResult = response.results;
});
Then I fetched the id for each person and passed in as a parameter to the function call for the request for the detail view.
I tried with console.log() to make sure I passed in the right value for the detail view request and then even hardcoded the
parameter for the request ie:
$scope.getInfoById = function(info_id) {
console.log("from HTML: "+info_id.value ); // = JK 00 46 67
$http.get("https://randomuser.me/api/?id="+'JK 00 46 67 H') ...
The jason data behind the API is formatted like this for the id-property:
{
"results": [
{
"id": {
"name": "BSN",
"value": "04242023"
},...
I still haven't figured out how to get the one user by id. Getting different users all the time, even with hard coded id...
Instead of making the second request my solution was to a pass the "clicked user" as a parameter for the detailed view.
Change your code to this:
$scope.fetchInfoById = function(info_id) {
$http.get("https://randomuser.me/api/?id="+info_id)
//also tried: $http.get("https://randomuser.me/api/?id/value="+info_id)
.success(function(data) {
$scope.oneUserResult = data.results;
});
}
Also, make sure you are passing in the correct value to this function.
Fetch a list of users from API call "https://randomuser.me/api/?results=5".
$scope.getAllUsers= function(resultCount) {
$http.get("https://randomuser.me/api/?results="+resultCount)
.success(function(data) {
$scope.users= data.results;
});
Display them on the screen.
On click of one record fetch details for that particular record from users list fetched earlier.
$scope.getUserById= function(userId) {
return $scope.users.filter(function(user) {
return user.id.value=== userId;
})[0]; // apply necessary null / undefined checks wherever required.
}
another way using ng-model:
$scope.user = {};
$scope.fetchInfoById = function() {
$http.get("https://randomuser.me/api/?id="$scope.user.id)
.success(function(data) {
$scope.oneUserResult = data.results;
});
}
Related
I've a problem that, unfortunately, I was not able to solve in a while, even looking at related StackOverflow Q/A.
I'm building an application using MEAN and I'm having an issue
when I need to render new items trough ng-repeat.
I have lots of items stored in a MongoDB instance, and I'm perfectly
able to fetch all of them trough API calls.
I need to show only 24 items at the very beginning, and 24 more every
time the user clicks on a show more button. I always need to
concatenate them after the old ones.
It works perfectly with the first 24 items but It does not render
other items.
When I try to log the new fetched items, I get them with no problems.
I'm able to see their attributes and so on.
This is a short cut of my items View:
<div class="myItem" ng-repeat="item in searchCtrl.items track by $index">
. . . .
</div>
This is my Show More Button:
<a class="showMoreButton" ng-click="searchCtrl.goToNextPage()">show more</a>
This is a simplified version of my Controller also known as searchCtrl:
function SearchController($scope, ItemFactory) {
var vm = this;
//Needed for pagination, 24 items at a time, starting from page 1
vm.searchParams = {
size : 24,
page : 1
}
//Initialize Empty Array to Contain Items
vm.items = [];
/*Calling fetchItems to fetch the items the very
first time the Controller is called*/
fetchItems();
//Calls goToPage passing it a new page (It handles pagination)
vm.goToNextPage = function() {
var next = parseInt(vm.info.currentPage) + 1;
vm.goToPage(next);
};
//Calls fetchItems after setting the new page
vm.goToPage = function(page) {
vm.searchParams.page = page;
fetchItems();
};
//Calls getItems and pushes the single items into vm.items
function fetchItems(){
ItemFactory.getItems(vm.searchParams).then(function(response){
//iterates trough items
for (var i = 0; i < response.data.data.length; i++) {
//Log current item
console.log(JSON.stringify(response.data.data[i]));
//push current item into vm.items
vm.items.push(response.data.data[i]);
}
//Print correctly the new items pool
console.log(vm.items);
}, function(error){
$log.error(error);
});
}
};
This is a simplified version of my ItemFactory:
angular.module('myApp').factory('ItemFactory',
function ($http, API_URL) {
//Getting items from API
function getItems(params) {
return $http.get(API_URL + '/item',{params: params}
).then(function success(response) {
return response;
});
}
return {
getItems : getItems
}
});
Controller binding to my view, it work as it should. I'm using this modularized approach and it always works perfectly:
'use strict';
angular.module('myApp')
.config(itemRoute);
function itemRoute($stateProvider) {
$stateProvider
.state('index.items', {
url : 'index/items',
parent : 'index',
templateUrl : 'app/main-pages/items/items.html',
controller : 'SearchController',
controllerAs : 'searchCtrl'
});
}
I also tried using concat instead of looping trough items with a for but the result does not change:
//Instead of looping
vm.items = vm.items.concat(response.data.data);
Essentially:
I'm only able to render the first 24 items
I can not render all the other items even if they get properly inserted into items array
Items starting from 25 and so on do not get into the DOM
I already tried using $scope.$apply(); but I get digest errors
Questions:
What is causing this?
How can I solve this issue?
Thanks in advance, if you need any clarification just post a comment below.
I managed to solve this issue broadcasting a message from ItemFactory when fetching new items and attaching a listener to that message in SearchController. When ItemDataRefresh gets broadcasted, then, SearchController concatenates the new data.
ItemFactory:
function getItems(params) {
return $http.get(API_URL + '/item',{params: params}
).then(function success(response) {
$rootScope.$broadcast('refreshData', response.data);
return response;
});
}
SearchController:
function fetchItems(){
ItemFactory.getItems(vm.searchParams).then(function(response){
//When vm.items is empty
if (!vm.items.length) {
vm.items = vm.items.concat(response.data.data);
}
//on refreshData
$scope.$on('refreshData', function(event, data){
vm.items = vm.items.concat(data.data);
});
}, function(error){
$log.error(error);
});
}
I know that I should not use rootScope so I'm still looking for a way to make it work in a cleaner way.
I hope it will help someone.
I am building a web application just as a test now. Right now the main features are login and posting a message.
When the user presses a button, the following code is executed:
$scope.getData = function(){
$http.get('/messages').
then(function(response) {
if(angular.toJson(response.data.messages) != angular.toJson($scope.messages)) {
$scope.messages = response.data.messages;
$scope.clearAllSelected();
$scope.confirmRestOperation(response);
}
}, function(response) {
$scope.messages = [];
$scope.confirmRestOperation(response);
});
};
The HTML template dependent off of my model is as follows:
<div id="messages">
<md-card ng-repeat="message in messages" class="padding-medium box-sizing" ng-init="setAuthorDataFromAuthorId(message.author)">
<md-checkbox class="md-primary" ng-click="toggle(message.id, selected)" ng-model="exists(message.id, selected)"></md-checkbox>
<h4><b>Author: {{authorData[message.author].name}} ({{authorData[message.author].username}})</b></h4>
<img style="width:50px;height:50px;" ng-src="{{authorData[message.author].profile_picture}}" alt="(Picture)">
<p>Message: {{message.message}}</p>
<p>{{message.time | timestampToDate}}</p>
</md-card>
</div>
So the goal here is, the message object will contain author field which contains the ID of the author. On each card initialization I call a $scope function called setAuthorDataFromAuthorId, this takes in an integer value, the ID of the author who wrote the post. The following code is this:
$scope.setAuthorDataFromAuthorId = function(id) { // line 22
console.log($scope.authorData); // line 23
if(id in $scope.authorData) return;
console.log("setAuthorFromAuthorId has been called");
$http.get('/getUserDataFromId/'+id).then(function(response) {
console.log(response.data); // line 27
console.log($scope.authorData); // line 28
$scope.authorData[id] = response.data;
}, function(response) {
console.log(response.data);
console.log($scope.authorData);
$scope.authorData = {};
});
console.log("setAuthorFromAuthorId has been completed");
};
See my goal here is say there are 50 messages on the screen (obviously not realistic in any production scenario to load every single message in the database, but for a test site this is what it's doing), I don't want it to access the api endpoint /getUserDataFromId/1 50 different times if the same message is from the same author. My goal here is to see if the id is already a key in our $scope.authorData object. If so, we return a save ourselves a trip to the endpoint and loading and writing information. So in a perfect world, all 50 messages would load and in the console we would see only this:
Object {}
setAuthorFromAuthorId has been called
setAuthorFromAuthorId has been completed
Object {id: author data here}
This would happen because it would load the author information one time, and the remaining 49 it would see that the id is a key in the authorData object and it would not get past the first line in the above JS function. However here are the results:
Object {} MessageController.js:23
setAuthorFromAuthorId has been called MessageController.js:25
setAuthorFromAuthorId has been completed MessageController.js:35
Object {} MessageController.js:23
setAuthorFromAuthorId has been called MessageController.js:25
setAuthorFromAuthorId has been completed MessageController.js:35
Object {full author data here from api response} MessageController.js:27
Object {} MessageController.js:28
Object {full author data here from api response} MessageController.js:27
Object {1: Object} (correct $scope.authorData) MessageController.js:28
I do not understand this output at all. It is as if the ng-init is only calling the couple lines of the function, and deferring the rest until later to actually work and update the model. I did some research and tried adding some $scope.$watch()'s and $scope.$apply()'s and digests after certain key lines of code to try and get the model to update properly but it keeps running the first console.log before the conditional as many times as there are messages, and then later it skips that part, and runs all the $http.get() as many times as there are messages, and undermines my want to save ourselves from reaching the API endpoint too many useless times.
After some thinking I decided to change the ng-init to ng-click and see if this worked at all. It was perfect. If there were 50 message all from the same user and I clicked on one, they all updated(because the model was properly updated) and if I clicked any more times after that the API would not be reached any more because the function is working how it lexically should be working. Here was the output after changing the ng-init to ng-click:
Object {} MessageController.js:23
setAuthorFromAuthorId has been called MessageController.js:25
setAuthorFromAuthorId has been completed MessageController.js:35
Object {full author data from API} MessageController.js:27
Object {} MessageController.js:28 // this is the authorData before it was updated with the above data
Object {1: Object} MessageController.js:23
Object {1: Object} MessageController.js:23
Object {1: Object} MessageController.js:23
Object {1: Object} MessageController.js:23 // this would happen as many times as I clicked, which is the correct response
Any recommendations as to how I can get this to work properly and as expected (to me at least) with ng-init or at least some reasoning as to why it is failing with ng-init?
My gut tells me perhaps I cam trying to read or write something a value before it has been written, this would warrant a $scope.$watch() however I have tried it (perhaps not correctly though!).
Thanks
Forget ng-init, move the initialization inside the controller.
Chain from the first $http request.
When the user presses a button, execute the following code:
$scope.getDataAndInitPromise = function() {
$scope.pendingFlags = $scope.pendingFlags || {};
var promises = [];
//compute getDataPromise
var getDataPromise = $scope.getDataPromise();
//chain from getDataPromise
var initListPromise = getDataPromise.then (function() {
angular.forEach($scope.messages, function(m) {
var id = m.author;
if ($scope.pendingFlags[id] == "pending") return;
if(id in $scope.authorData) return;
console.log("setAuthorFromAuthorId has been called");
//set pending flag
$scope.pendingFlags[id] = "pending";
var p = $http.get('/getUserDataFromId/'+id
).then(function(response) {
console.log($scope.authorData);
$scope.authorData[id] = response.data;
}).catch(function(error) {
//log error
}).finally ( function() {
console.log("setAuthorId %s done ", id);
//clear pending flag
$scope.pendingFlags[id] = "done";
});
//push out promises
promises.push(p);
});
//return promises array for further chaining
return promises;
});
//return for further chaining
return initListPromise;
}
Notice the use of pendingFlags to prevent a repeat fetch if a fetch is already in process. Also notice that I moved the console.log report of "done" into a .finally method.
Of course modify, getData to return a promise for chaining.
$scope.getDataPromise = function(){
var getDataPromise =
$http.get('/messages').
then(function(response) {
if(angular.toJson(response.data.messages) != angular.toJson($scope.messages)) {
$scope.messages = response.data.messages;
$scope.clearAllSelected();
$scope.confirmRestOperation(response);
}
}, function(response) {
$scope.messages = [];
$scope.confirmRestOperation(response);
});
//return promise for chaining
return getDataPromise;
};
Leverage the power of promises.
Don't throw away those httpPromises; use them.
I am trying to figure this out the best way to do this. I am trying to insert data into a WebSQL table and then select the data from the table and display on the screen using ng-repeat. I am using this Angular WebSQL Module https://github.com/paulocaldeira17/angular-websql#select-all.
So far, I can get the remote data and insert them into the local database. When I try to call the insert data, $scope.localproducts shows an empty array - console.log( $scope.localproducts) shows an empty array.
I use localproducts scope for my ng-repeat.
I can't get to return the ProductsFactory.localproducts array to my controller from the Factory's selectAllData function.
When clicks a button on my page, it calls the insertData function in my Controller.
What have I done wrong here? I am pretty new to angular so I would very much appreciate if someone can help me to improve the below code or suggest if there is a better way to do this.
.controller('DownloadProductsCtrl', ['$scope','ProductsFactory', function ($scope, ProductsFactory){
$scope.products = ProductsFactory.products;
$scope.localproducts = ProductsFactory.localproducts;
$scope.insertData = function(){
ProductsFactory.getRemoteData().then(function(results){
$scope.localproducts = ProductsFactory.localproducts;
console.log( $scope.localproducts); //This shows an empty array
});
}; }])
.factory('ProductsFactory', ['$webSql', function($webSql){
db = $webSql.openDatabase('myappdb', '1.0', 'Test DB', 2 * 1024 * 1024);
ProductsFactory = {};
ProductsFactory.products = [];
ProductsFactory.localproducts = [];
ProductsFactory.getRemoteData = function () {
return $http.get('./products/list.json')
.success(function (data) {
ProductsFactory.products = data;
ProductsFactory.insertData(data);
})
.error(function () {
console.error('Error');
});
};
ProductsFactory.insertData = function (data){
angular.forEach(data, function(value, key) {
db.insert('products', value).then(function(results) {
<!-- In here I like to count the total inserted items and display it on the page, but not sure sure how to send it to a scope in my controller -->
});
});
ProductsFactory.selectAllData();
};
ProductsFactory.selectAllData = function(){
db.selectAll("products").then(function(results) {
for(var i=0; i < results.rows.length; i++){
ProductsFactory.localproducts.push(results.rows.item(i)); //This added data to the array successfully.
}
console.log(ProductsFactory.localproducts); //This shows an empty array
});
};
return ProductsFactory;
}]);
Try with this resource as a start point.
https://gist.github.com/jgoux/10738978
https://github.com/paulocaldeira17/angular-websql/blob/master/angular-websql.js
The first one is very basic and easier to understand. The second more involved.
Let me explain my issue, I am trying to populate Ember.Select directly from database.
I have these routes:
this.resource('twod', function() {
this.resource('twoduser', {
path : ':user_id'
});
});
In twoduser, I am displaying a full information about a single user. In that view, I have a Select Box as well, which end user will select and then with a button, he can add the user to a team that he selected from Ember.Select.
I tried to do this,
App.TwoduserController = Ember.ArrayController.extend({
selectedTeam : null,
team : function (){
var teams = [];
$.ajax({
type : "GET",
url : "http://pioneerdev.us/users/getTeamNames",
data : data,
success : function (data){
for (var i = 0; i < data.length; i ++){
var teamNames = data[i];
teams.push(teamNames);
}
}
});
return teams;
}.property()
})
Then in my index.html:
{{view Ember.Select
contentBinding="team"
optionValuePath="teams.team_name"
optionLabelPath="teams.team_name"
selectionBinding="selectedTeam"
prompt="Please Select a Team"}}
But when I do this, for some reason it interferes with Twoduser and I am not able to view the single user.
Furthermore, here's a sample JSON response I will get through the url:
{"teams":[{"team_name":"Toronto Maple Leafs"},{"team_name":"Vancouver Canuck"}]}
Moreover, I am fetching all users using Ajax like this:
App.Twod.reopenClass({
findAll : function() {
return new Ember.RSVP.Promise(function(resolve, reject) {
$.getJSON("http://pioneerdev.us/users/index", function(data) {
var result = data.users.map(function(row) {
return App.Twod.create(row);
});
resolve(result);
}).fail(reject);
});
},
findBy : function(user_id) {
return new Ember.RSVP.Promise(function(resolve, reject) {
var user = App.Twod.create();
$.getJSON("http://pioneerdev.us/users/byId/" + user_id, function(data) {
var result = user.setProperties(data.user);
resolve(result);
}).fail(reject);
});
}
});
Though there's one thing, I have a separate Teams route:
this.resource('teamview', function(){
this.resource('teamviewdetail', {
path : ':team_id'
});
});
Which shows all the teams and a single team when you click on a single team.
Can I use that TeamviewController? or Can I fetch team names from Twoduser Controller and push names to the array as I mentioned before?
More Information:
If I use the way I mentioned, I get this error:
Uncaught TypeError: Object [object Object] has no method 'addArrayObserver'
Here's a working jsfiddle on the issue I am experiencing. You can select "Storyboard" from the Designation & then select the user. That will reproduce the issue.
One more Update: Seems using ObjectController instead of ArrayController issue solves the addArrayObserver issue. But still I can't get the teams in the Ember.Select.
The biggest issue here is that you use Array#push instead of pushObject. Ember needs the special methods in order to be aware of changes. Otherwise, it will continue to think that the array of teams is as empty as when you first returned it. Second biggest issue is that your ajax success call isn't accessing the returned data properly.
Also, optionValuePath and optionLabelPath are relative to the individual select option view, so they should start with content, which is the individual item as set on the view. So: content.team_name
I'm trying to grab all the URLs of my Facebook photos.
I first load the "albums" array with the album id's.
Then I loop through the albums and load the "pictures" array with the photos URLs.
(I see this in Chrome's JS debugger).
But when the code gets to the last statement ("return pictures"), "pictures" is empty.
How should I fix this?
I sense that I should use a closure, but not entirely sure how to best do that.
Thanks.
function getMyPhotos() {
FB.api('/me/albums', function(response) {
var data = response.data;
var albums = [];
var link;
var pictures = [];
// get selected albums id's
$.each(data, function(key, value) {
if ((value.name == 'Wall Photos')) {
albums.push(value.id);
}
});
console.log('albums');
console.log(albums);
// get the photos from those albums
$.each(albums, function(key, value) {
FB.api('/' + value + '/photos', function(resp) {
$.each(resp.data, function(k, val) {
link = val.images[3].source;
pictures.push(link);
});
});
});
console.log('pictures');
console.log(pictures);
return pictures;
});
}
You're thinking about your problem procedurally. However, this logic fails anytime you work with asynchronous requests. I expect what you originally tried to do looked something like this:
var pictures = getMyPhotos();
for (var i = 0; i < pictures.length; i++) {
// do something with each picture
}
But, that doesn't work since the value of 'pictures' is actually undefined (which is the default return type of any function without an actual return defined -- which is what your getMyPhotos does)
Instead, you want to do something like this:
function getMyPhotos(callback) {
FB.api('/me/albums', function (response) {
// process respose data to get a list of pictures, as you have already
// shown in your example
// instead of 'returning' pictures,
// we just call the method that should handle the result
callback(pictures);
});
}
// This is the function that actually does the work with your pictures
function oncePhotosReceived(pictures){
for (var i = 0; i < pictures.length; i++) {
// do something with each picture
}
};
// Request the picture data, and give it oncePhotosReceived as a callback.
// This basically lets you say 'hey, once I get my data back, call this function'
getMyPhotos(oncePhotosReceived);
I highly recommend you scrounge around SO for more questions/answers about AJAX callbacks and asynchronous JavaScript programming.
EDIT:
If you want to keep the result of the FB api call handy for other code to use, you can set the return value onto a 'global' variable in the window:
function getMyPhotos(callback) {
FB.api('/me/albums', function (response) {
// process respose data to get a list of pictures, as you have already
// shown in your example
// instead of 'returning' pictures,
// we just call the method that should handle the result
window.pictures = pictures;
});
}
You can now use the global variable 'pictures' (or, explicitly using window.pictures) anywhere you want. The catch, of course, being that you have to call getMyPhotos first, and wait for the response to complete before they are available. No need for localStorage.
As mentioned in the comments, asynchronous code is like Hotel California - you can check any time you like but you can never leave.
Have you noticed how the FB.api does not return a value
//This is NOT how it works:
var result = FB.api('me/albums')
but instead receives a continuation function and passes its results on to it?
FB.api('me/albums', function(result){
Turns out you need to have a similar arrangement for your getMyPhotos function:
function getMyPhotos(onPhotos){
//fetches the photos and calls onPhotos with the
// result when done
FB.api('my/pictures', function(response){
var pictures = //yada yada
onPhotos(pictures);
});
}
Of course, the continuation-passing style is contagious so you now need to call
getMyPhotos(function(pictures){
instead of
var pictures = getMyPhotos();