View not updating when pushing new items in an Array - AngularJS - javascript

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.

Related

Using $rootScope and $broadcast to communicate between functions

In my app I have one page that shows the details of a particular users document in which a document can be deleted and another that shows the users recently viewed documents. I'm trying to make it so when a user deletes a document in the view page it also deletes the listing in the users recently viewed documents history.
I'm attempting to use $broadcast and $rootScope to communicate between the two modules but I suspect I'm doing something wrong with the syntax or there's an issue with scope or project structure.
Here's the two separate functions that delete the different entries
history.js
//gets the selected item the user clicks and deletes it and updates history
$scope.removeFavorite = function(item) {
var items = $scope.recent[item.type];
item = items.splice(items.indexOf(item), 1)[0];
$rootScope.$on('deleteRecent', function(data) {
historyManager.remove(data);
});
historyManager.remove(item).then(loadHistoryItems, loadHistoryItems);
};
documentView.js
//confirmation that user wants selected document deleted
function confirmDelete() {
var delObj = _.pick(sDocument, 'Doc_Type', 'Doc_Num');
repos
.sDoc
.del(delObj);
var item = sDocument.Doc_Type + ';' + sDocument.Doc_Num;
$rootScope.$broadcast('deleteRecent', item);
tabBarViewModel.removeTabByState($state.get('sDocument'), delObj);
$scope.modalOptions.hide();
}
Given that your rootScope injection is fine your problematic part is this one:
$scope.removeFavorite = function(item) {
var items = $scope.recent[item.type];
item = items.splice(items.indexOf(item), 1)[0];
$rootScope.$on('deleteRecent', function(data) {
historyManager.remove(data);
});
historyManager.remove(item).then(loadHistoryItems, loadHistoryItems);
};
First if you're using $rootScope to broadcast, message can be received on any $scope, other then that, you don't wrap it into function on $scope and first parameter is event.
So correct code would look like this:
$scope.$on('deleteRecent', function(event, data) {
historyManager.remove(data);
});
$scope.removeFavorite = function(item) {
var items = $scope.recent[item.type];
item = items.splice(items.indexOf(item), 1)[0];
historyManager.remove(item).then(loadHistoryItems, loadHistoryItems);
};

Random User Generator API - get user by ID Angular JS

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;
});
}

Angular JS : how can I load the factory only once?

I am using a static json file to simulate my server and getting my array of orders from it.
I'm presenting the orders in a table in my html file with the option of deleting one from it.
Each time I load the html file the full list gets loaded, with the orders I have deleted throught the controller function.
How can I loat the data from the factory only once?
Here is my controller:
app.controller("MainPageCtrl", function($scope, getOrdersFactory)
{
$scope.orders = [];
// Getting the data frm the facrory
var dataPromise = getOrdersFactory.getDataFunc();
dataPromise.then(function(data){
$scope.orders = data.orders;
});
// Deletes an orders.
$scope.deleteOrder = function(order){
// Finds the index of the order.
var orderIndex = $scope.orders.indexOf(order);
// Delete the order.
$scope.orders.splice(orderIndex, 1);
};
});
By default angular services and factories are singletons(loaded only once). The problem you are facing is with controller re-initialization. When route change happens the controller is re-initialized so therby getting the previous value from the factory.
You can use a setter function on your 'getOrdersFactory'.
Assuming your 'getOrdersFactory' to be
app.factory('getOrdersFactory',function(){
//code to read from file and set the file on a variable orderDetails
var orderDetails = contentsReadFromFile;
return{
getDataFunc:function(){
return orderDetails
},
setDataFunc:function(modifiedOrderDetails){
orderDetails = modifiedOrderDetails;
//code to set the new content to the static file
}
}
}
code to read the file from the static file will be rendered when you inject the factory for the first time, and on your controller set the order details on the delete function
// Deletes an orders.
$scope.deleteOrder = function(order){
// Finds the index of the order.
var orderIndex = $scope.orders.indexOf(order);
// Delete the order.
$scope.orders.splice(orderIndex, 1);
getOrdersFactory.setDataFunc($scope.orders);
};
I guess you are losing your data i.e $scope.orders .If this is the scenario just change
dataPromise.then(function(data){
$scope.orders = data.orders;
});
to
dataPromise.then(function(data){
$scope.orders = angular.copy(data.orders);
});

Select websql Data using Angular JS

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.

Populate Ember.Select directly from Database

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

Categories