updating the result of a function realtime - javascript

Below is the code i use to query my firebase in search of players whose ids match the numbers in an array:
.controller('mySquadCtrl', ['$scope','$location','$firebaseArray','$firebaseAuth','CommonProp', 'DatabaseService',
function($scope,$location,$firebaseArray,$firebaseAuth,CommonProp, databaseService){
var uid = CommonProp.userUid();
$scope.uid = uid;
$scope.username = CommonProp.getUser();
if(!$scope.username){
$location.path('/welcome');
}
databaseService.users.child(uid).once('value', function(usersSnapshot){
var users = usersSnapshot.val();
var total = users.total;
var team = users.teamname;
$scope.pick = total;
var orderedPlayers = databaseService.players.orderByChild("id");
$firebaseArray(orderedPlayers)
.$loaded(function(loadedPlayers) {
var normalizedPlayers = loadedPlayers.reduce(function(acc, next) { acc[next.id] = next; return acc; }, {});
var selectedPlayers = $scope.pick.map(function(num){
return normalizedPlayers[num];
});
$scope.players = selectedPlayers;
$scope.sum = function(items, prop){
return items.reduce( function(a, b){
return a + b[prop];
}, 0);
};
$scope.totalPoints = $scope.sum($scope.players, 'goals');
$scope.teamname = team;
}, function(err) {
console.log(err);
$scope.players = [];
});
});
$scope.logout = function(){
CommonProp.logoutUser();
};
}]);
If the user is not logged in, the page redirects them to the 'home page'.
My issue is that the $scope.totalPoints result does not update in realtime even though the $scope.players "goal" value does when i change it in the database directly.
IS there a way to make the $scope.totalPoints update in realtime as I change the values to the $scope.players values in the database?

The $loaded promise is only called once: when the initial data has been loaded from the database. It is not called on updates.
To have an auto-updating sum, you'll instead want to extend $firebaseArray. See the AngularFire documentation for an example of extending an array to include a total. For another good example, see the answer to this question.

Related

Updating $scope with firebase

Im trying to update the scope after I get the response from Firebase (I am doing it in the app controler part), here is a sample code.
PS I am using AngularFire ,Firebase and Angularjs
Thank You
var app = angular.module("sampleApp", ["firebase"]);
firebase.initializeApp(config);
app.factory("mealMacros", ["$firebaseArray",
function($firebaseArray) {
var dbRef = firebase.database();
var userId = sessionStorage.uid;
var ordersRef = firebase.database().ref('user_info/' + userId + '/orders');
return $firebaseArray(ordersRef);
}
]);
app.controller("MealCtrl", ["$scope", "mealMacros",
function($scope, mealMacros) {
// $scope.user = "Guest " + Math.round(Math.random() * 100);
//tried mealMacros.on.. didnt work
ordersRef.on('value', function(snapshot) {
var orders = snapshot.val();
// Loop and parse order ids here
for (var key in orders) {
var orderId = orders[key]['orderType'];
console.log(orderId);
$scope.products = orderId;
//code not reaching here
}
});
}
]);
ordersRef.on('value', function(snapshot) {
var orders = snapshot.val();
// Loop and parse order ids here
for (var key in orders) {
var orderId = orders[key]['orderType'];
console.log(orderId);
$scope.products = orderId;
//code not reaching here
}
});
First you need to make sure you get data in var orders
Then $scope.products = orderId; you set the same variable in a loop. Maybe $scope.products is an array and you want to push data in it? $scope.products.push(orderId);
Finally, setting a $scope variable inside a .on event, you will need to call $scope.$apply() before the function returns.
ordersRef.on('value', function(snapshot) {
var orders = snapshot.val();
console.log(orders); // make sure there's an array here
for (var key in orders) {
var orderId = orders[key]['orderType'];
$scope.products.push(orderId);
}
$scope.$apply();
});
One last thing, var orderId = orders[key]['orderType']; may not work as expected depending on the structure of your var orders array.
You can try var orderId = key.orderType; if that doesn't work.

How to copy Array from Server to existing Array reference

I've been trying to code up a search engine using angular js, but I can't copy one array to another. When I initiate the the code (in the service.FoundItems in the q.all then function) new array(foundArray) shows up as an empty array. I searched up how to copy one array to another and tried that method as you can see, but it isn't working. Please help, here is the code, and thank you.
P.S. if you need the html please tell me.
(function () {
'use strict';
angular.module('narrowDownMenuApp', [])
.controller('narrowItDownController', narrowItDownController)
.service('MenuSearchService', MenuSearchService)
.directive('searchResult', searchResultDirective);
function searchResultDirective() {
var ddo = {
templateUrl: 'searchResult.html',
scope: {
items: '<'
},
};
return ddo
}
narrowItDownController.$inject = ['MenuSearchService'];
function narrowItDownController(MenuSearchService) {
var menu = this;
menu.input = "";
menu.displayResult = [];
menu.searchX = function(name) {
menu.displayResult = MenuSearchService.FoundItems(menu.input, name);
console.log(menu.displayResult);
};
}
MenuSearchService.$inject = ['$http', '$q'];
function MenuSearchService($http, $q) {
var service = this;
service.getMatchedMenuItems = function(name, searchTerm) {
var deferred = $q.defer();
var foundItems = [];
var result = $http({
method: "GET",
url: ('https://davids-restaurant.herokuapp.com/menu_items.json'),
params: {
category: name
}
}).then(function (result) {
var items = result.data;
for (var i = 0; i < items.menu_items.length; i++) {
if (searchTerm === ""){
deferred.reject("Please enter search term");
i = items.menu_items.length;
}
else if (items.menu_items[i].name.toLowerCase().indexOf(searchTerm.toLowerCase()) ==! -1){
foundItems.push(items.menu_items[i].name)
deferred.resolve(foundItems);
}else {
console.log("doesn't match search");
}
}
});
return deferred.promise;
};
service.FoundItems = function (searchTerm, name) {
var searchResult = service.getMatchedMenuItems(name, searchTerm);
var foundArray = [];
$q.all([searchResult])
.then(function (foundItems) {
foundArray = foundItems[0].slice(0);
foundArray.reverse();
})
.catch(function (errorResponse) {
foundArray.push(errorResponse);
});
console.log(foundArray);
return foundArray;
};
};
})();
If the goal of the service.FoundItems function is to return a reference to an array that is later populated with results from the server, use angular.copy to copy the new array from the server to the existing array:
service.FoundItems = function (searchTerm, name) {
var foundArray = [];
var searchPromise = service.getMatchedMenuItems(name, searchTerm);
foundArray.$promise = searchPromise
.then(function (foundItems) {
angular.copy(foundItems, foundArray);
foundArray.reverse();
return foundArray;
})
.catch(function (errorResponse) {
return $q.reject(errorResponse);
})
.finally(function() {
console.log(foundArray);
});
return foundArray;
};
I recommend that the promise be attached to the array reference as a property named $promise so that it can be used to chain functions that depend on results from the server.
Frankly I don't recommend designing services that return array references that are later populated with results. If you insist on designing it that way, this is how it is done.
I tried the $promise thing that you recommended. I was wondering how you would get the value from it ie the array.
In the controller, use the .then method of the $promise to see the final value of the array:
narrowItDownController.$inject = ['MenuSearchService'];
function narrowItDownController(MenuSearchService) {
var menu = this;
menu.input = "";
menu.displayResult = [];
menu.searchX = function(name) {
menu.displayResult = MenuSearchService.FoundItems(menu.input, name);
̶c̶o̶n̶s̶o̶l̶e̶.̶l̶o̶g̶(̶m̶e̶n̶u̶.̶d̶i̶s̶p̶l̶a̶y̶R̶e̶s̶u̶l̶t̶)̶;̶
menu.displayResult.$promise
.then(function(foundArray) {
console.log(foundArray);
console.log(menu.displayResult);
}).catch(function(errorResponse) {
console.log("ERROR");
console.log(errorResponse);
});
};
}
To see the final result, the console.log needs to be moved inside the .then block of the promise.
Titus is right. The function always immediately returns the initial value of foundArray which is an empty array. The promise is executed asynchronously so by the time you are trying to change foundArray it is too late. You need to return the promise itself and then using .then() to retrieve the value just like you are currently doing inside the method.
From just quickly looking at your code I think you made have a simple error in there. Are you sure you want
foundArray = foundItems[0].slice(0);
instead of
foundArray = foundItems.slice(0);

how to access data in a angularjs promise?

I'm trying to access a return promise data, but it always give me undefined even when i'm accessing to the correct element.
here is why i have to do it that way .
controller.js
// can't access the data "tastes" in success outside from the .success call
// so i return the entire http call and access it later
function loadContacts() {
var contacts;
return Account.getTastes()
.success(function(tastes) {
contacts= tastes[0].tastes;
return contacts.map(function (c, index) {
var colors = ["1abc9c", "16a085", "f1c40f", "f39c12", "2ecc71", "27ae60", "e67e22","d35400","3498db","2980b9","e74c3c","c0392b","9b59b6","8e44ad","34495e","2c3e50"];
var color = colors[Math.floor(Math.random() * colors.length)];
var cParts = c.split(' ');
var contact = {
name: c,
image: "http://dummyimage.com/50x50/"+color+"/f7f7f7.png&text="+c.charAt(0)
};
contact._lowername = contact.name.toLowerCase();
return contact;
});
})
.error(function(error) {
console.log("Error:"+error)
});
}
/**
* Search for contacts.
*/
function querySearch (query) {
var results = query ?
$scope.allContacts.filter(createFilterFor(query)) : [];
return results;
}
/**
* Create filter function for a query string
*/
function createFilterFor(query) {
var lowercaseQuery = angular.lowercase(query);
return function filterFn(contact) {
return (contact._lowername.indexOf(lowercaseQuery) != -1);;
};
}
$scope.allContacts = loadContacts();
console.log($scope.allContacts)
$scope.tags = [$scope.allContacts[0],$scope.allContacts[1]];
// since $scope.allContacts is undefined thus,
//i'm having error for trying to access element[0] of undefined
the console.log($scope.allContacts) result is
but when i try to access it like $scope.allContacts.$$state.value.data i get undefined as return. may i know what is the solution for this ?
in my html
<md-contact-chips required ng-model="tags" md-contacts="querySearch($query)" md-contact-name="name" md-contact-image="image" filter-selected="true" placeholder="Thai, chinese, cheap, cafe and etc">
</md-contact-chips>
UPDATE: 1(SOLVED)
i'm getting this error after i change my code to
$scope.allContacts.then(function(contacts){
$scope.tags = [contacts[0],contacts[1]];
})
Error: [ngRepeat:dupes] Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: $chip in $mdChipsCtrl.items, Duplicate key: undefined:undefined, Duplicate value: undefined
i'm not even using ng-repeat why am i still getting ng-repeat error ?
UPDATE: 2
the chips is now being displayed however i can't search, when i try to search on the md-contact-chips this happened
You should be able to access contacts like so...
$scope.allContacts.then(function(contacts){
console.log(contacts[0]);
console.log(contacts[1]);
})
if you change
Account.getTastes().success
to
Account.getTastes().then
final results
function loadContacts() {
var contacts;
return Account.getTastes()
.then(function(res) {
contacts = res.data[0].tastes;
return contacts.map(function (c, index) {
var colors = ["1abc9c", "16a085", "f1c40f", "f39c12", "2ecc71", "27ae60", "e67e22","d35400","3498db","2980b9","e74c3c","c0392b","9b59b6","8e44ad","34495e","2c3e50"];
var color = colors[Math.floor(Math.random() * colors.length)];
var cParts = c.split(' ');
var contact = {
name: c,
image: "http://dummyimage.com/50x50/"+color+"/f7f7f7.png&text="+c.charAt(0)
};
contact._lowername = contact.name.toLowerCase();
return contact;
});
})
.error(function(error) {
console.log("Error:"+error)
});
}
$scope.allContacts = loadContacts();
console.log($scope.allContacts)
$scope.allContacts.then(function(contacts){
console.log(contacts[0]);
console.log(contacts[1]);
})
The main problem here seems to be that when you try to access the value, the promise is not resolved yet, so I guess it should be enough to wrap
$scope.tags = [$scope.allContacts[0],$scope.allContacts[1]];
into something like
$scope.allContacts.then(function(){
$scope.tags = [$scope.allContacts[0],$scope.allContacts[1]];
}

Pull to refresh data duplication

I am creating an Ionic application that is pulling articles from a joomla K2 website. I am using $http and just ending my url off with '?format=json' and that is working perfectly. However the website I am pulling data from updates its articles every few minutes so I need a way for the user to be able to refresh the page. I have implemented Ionics pull to refresh and it is working swell except for the fact that instead of just pulling in new articles it just appends all the articles to my array. Is there anyway to just maybe iterate over current articles timestamps or IDs (I am caching articles in localStorage) to just bring in new articles? My factory looks like this:
.factory('Articles', function ($http) {
var articles = [];
storageKey = "articles";
function _getCache() {
var cache = localStorage.getItem(storageKey );
if (cache)
articles = angular.fromJson(cache);
}
return {
all: function () {
return $http.get("http://jsonp.afeld.me/?url=http://mexamplesite.com/index.php?format=json").then(function (response) {
articles = response.data.items;
console.log(response.data.items);
return articles;
});
},
getNew: function () {
return $http.get("http://jsonp.afeld.me/?url=http://mexamplesite.com/index.php?format=json").then(function (response) {
articles = response.data.items;
return articles;
});
},
get: function (articleId) {
if (!articles.length)
_getCache();
for (var i = 0; i < articles.length; i++) {
if (parseInt(articles[i].id) === parseInt(articleId)) {
return articles[i];
}
}
return null;
}
}
});
and my controller:
.controller('GautengCtrl', function ($scope, $stateParams, $timeout, Articles) {
$scope.articles = [];
Articles.all().then(function(data){
$scope.articles = data;
window.localStorage.setItem("articles", JSON.stringify(data));
},
function(err) {
if(window.localStorage.getItem("articles") !== undefined) {
$scope.articles = JSON.parse(window.localStorage.getItem("articles"));
}
}
);
$scope.doRefresh = function() {
Articles.getNew().then(function(articles){
$scope.articles = articles.concat($scope.articles);
$scope.$broadcast('scroll.refreshComplete');
});
};
})
Use underscore.js to simple filtering functionality.
For example:
get all id's of already loaded items (I believe there is some unique fields like id)
http://underscorejs.org/#pluck
var loadedIds = _.pluck($scope.articles, 'id');
Reject all items, if item.id is already in loadedIds list.
http://underscorejs.org/#reject
http://underscorejs.org/#contains
var newItems = _.reject(articles, function(item){
return _.contains(loadedIds, item.id);
});
Join new items and existings:
$scope.articles = newItems.concat($scope.articles);
or
http://underscorejs.org/#union
$scope.articles = _.union(newItems, $scope.articles);
Actually _.union() can manage and remove duplicates, but I would go for manual filtering using item.id.

Angularjs must refresh page to see changes

What I have is simple CRUD operation. Items are listed on page, when user clicks button add, modal pops up, user enters data, and data is saved and should automatically (without refresh)be added to the list on page.
Service:
getAllIncluding: function(controllerAction, including) {
var query = breeze.EntityQuery.from(controllerAction).expand(including);
return manager.executeQuery(query).fail(getFailed);
},
addExerciseAndCategories: function(data, initialValues) {
var addedExercise = manager.createEntity("Exercise", initialValues);
_.forEach(data, function(item) {
manager.createEntity("ExerciseAndCategory", { ExerciseId: addedExercise._backingStore.ExerciseId, CategoryId: item.CategoryId });
});
saveChanges().fail(addFailed);
function addFailed() {
removeItem(items, item);
}
},
Controller:
$scope.getAllExercisesAndCategories = function() {
adminCrudService.getAllIncluding("ExercisesAndCategories", "Exercise,ExerciseCategory")
.then(querySucceeded)
.fail(queryFailed);
};
function querySucceeded(data) {
$scope.queryItems = adminCrudService.querySucceeded(data);
var exerciseIds = _($scope.queryItems).pluck('ExerciseId').uniq().valueOf();
$scope.exerciseAndCategories = [];
var createItem = function (id, exercise) {
return {
ExerciseId: id,
Exercise : exercise,
ExerciseCategories: []
};
};
// cycle through ids
_.forEach(exerciseIds, function (id) {
// get all the queryItems that match
var temp = _.where($scope.queryItems, {
'ExerciseId': id
});
// go to the next if nothing was found.
if (!temp.length) return;
// create a new (clean) item
var newItem = createItem(temp[0].ExerciseId, temp[0].Exercise);
// loop through the queryItems that matched
_.forEach(temp, function (i) {
// if the category has not been added , add it.
if (_.indexOf(newItem.ExerciseCategories, i.ExerciseCategory) < 0) {
newItem.ExerciseCategories.push(i.ExerciseCategory);
}
});
// Add the item to the collection
$scope.items.push(newItem);
});
$scope.$apply();
}
Here is how I add new data from controller:
adminCrudService.addExerciseAndCategories($scope.selectedCategories, { Name: $scope.NewName, Description: $scope.NewDesc });
So my question is, why list isn't updated in real time (when I hit save I must refresh page).
EDIT
Here is my querySuceeded
querySucceeded: function (data) {
items = [];
data.results.forEach(function(item) {
items.push(item);
});
return items;
}
EDIT 2
I believe I've narrowed my problem !
So PW Kad lost two hours with me trying to help me to fix this thing (ad I thank him very very very much for that), but unfortunately with no success. We mostly tried to fix my service, so when I returned to my PC, I've again tried to fix it. I believe my service is fine. (I've made some changes as Kad suggested in his answer).
I believe problem is in controller, I've logged $scope.items, and when I add new item they don't change, after that I've logged $scope.queryItems, and I've noticed that they change after adding new item (without refresh ofc.). So probably problem will be solved by somehow $watching $scope.queryItems after loading initial data, but at the moment I'm not quite sure how to do this.
Alright, I am going to post an answer that should guide you on how to tackle your issue. The issue does not appear to be with Breeze, nor with Angular, but the manner in which you have married the two up. I say this because it is important to understand what you are doing in order to understand the debug process.
Creating an entity adds it to the cache with an entityState of isAdded - that is a true statement, don't think otherwise.
Now for your code...
You don't have to chain your query execution with a promise, but in your case you are returning the data to your controller, and then passing it right back into some function in your service, which wasn't listed in your question. I added a function to replicate what yours probably looks like.
getAllIncluding: function(controllerAction, including) {
var query = breeze.EntityQuery.from(controllerAction).expand(including);
return manager.executeQuery(query).then(querySucceeded).fail(getFailed);
function querySucceeded(data) {
return data.results;
}
},
Now in your controller simply handle the results -
$scope.getAllExercisesAndCategories = function() {
adminCrudService.getAllIncluding("ExercisesAndCategories", "Exercise,ExerciseCategory")
.then(querySucceeded)
.fail(queryFailed);
};
function querySucceeded(data) {
// Set your object directly to the data.results, because that is what we are returning from the service
$scope.queryItems = data;
$scope.exerciseAndCategories = [];
Last, let's add the properties we create the entity and see if that gives Angular a chance to bind up properly -
_.forEach(data, function(item) {
var e = manager.createEntity("ExerciseAndCategory");
e.Exercise = addedExercise; e.Category: item.Category;
});
So I've managed to solve my problem ! Not sure if this is right solution but it works now.
I've moved everything to my service, which now looks like this:
function addCategoriesToExercise(tempdata) {
var dataToReturn = [];
var exerciseIds = _(tempdata).pluck('ExerciseId').uniq().valueOf();
var createItem = function (id, exercise) {
return {
ExerciseId: id,
Exercise: exercise,
ExerciseCategories: []
};
};
// cycle through ids
_.forEach(exerciseIds, function (id) {
// get all the queryItems that match
var temp = _.where(tempdata, {
'ExerciseId': id
});
// go to the next if nothing was found.
if (!temp.length) return;
// create a new (clean) item
var newItem = createItem(temp[0].ExerciseId, temp[0].Exercise);
// loop through the queryItems that matched
_.forEach(temp, function (i) {
// if the category has not been added , add it.
if (_.indexOf(newItem.ExerciseCategories, i.ExerciseCategory) < 0) {
newItem.ExerciseCategories.push(i.ExerciseCategory);
}
});
// Add the item to the collection
dataToReturn.push(newItem);
});
return dataToReturn;
}
addExerciseAndCategories: function (data, initialValues) {
newItems = [];
var addedExercise = manager.createEntity("Exercise", initialValues);
_.forEach(data, function (item) {
var entity = manager.createEntity("ExerciseAndCategory", { ExerciseId: addedExercise._backingStore.ExerciseId, CategoryId: item.CategoryId });
items.push(entity);
newItems.push(entity);
});
saveChanges().fail(addFailed);
var itemsToAdd = addCategoriesToExercise(newItems);
_.forEach(itemsToAdd, function (item) {
exerciseAndCategories.push(item);
});
function addFailed() {
removeItem(items, item);
}
}
getAllExercisesAndCategories: function () {
var query = breeze.EntityQuery.from("ExercisesAndCategories").expand("Exercise,ExerciseCategory");
return manager.executeQuery(query).then(getSuceeded).fail(getFailed);
},
function getSuceeded(data) {
items = [];
data.results.forEach(function (item) {
items.push(item);
});
exerciseAndCategories = addCategoriesToExercise(items);
return exerciseAndCategories;
}
And in controller I have only this:
$scope.getAllExercisesAndCategories = function () {
adminExerciseService.getAllExercisesAndCategories()
.then(querySucceeded)
.fail(queryFailed);
};
function querySucceeded(data) {
$scope.items = data;
$scope.$apply();
}

Categories