Here is my controller
angular.module("app").controller('myController', ['$scope', '$filter','$rootScope','contentService','$location','$anchorScroll', function ($scope, $filter,$rootScope,contentService,$location,$anchorScroll) {
$scope.searchContents = [] ;
var filterList = function (list, keyword) {
return $filter('filter')(list, keyword);
};
var addToSearchContents = function (list,type){
_.each(list,function(item){
item.type = type;
$scope.searchContents.push(item);
});
};
$scope.init = function(){
var str = $location.absUrl();
$scope.searchKeyword = str.substring(str.indexOf("=") + 1,str.length);
!_.isEmpty($scope.searchKeyword)
{
// get all songs
contentService.getAllSongs().then(function (result) {
var filteredSongs = filterList(result.data.songs, $scope.searchKeyword);
addToSearchContents(filteredSongs,"song");
});
// get all people
contentService.getAllPeople().then(function (result) {
var filteredPeople = filterList(result.data.people, $scope.searchKeyword);
addToSearchContents(filteredPeople,"people");
});
_.each($scope.searchContents,function(item){
alert("item -> "+item.type);
});
}
};
$scope.init();
}]);
Items(objects) are added to the variable $scope.searchContents in addToSearchContents but if i try to access/iterate after all objects that are pushed to $scope.searchContents with _.each it seems to null. But i can access all the contents in HTML page with ng-repeat but not in controller. I am puzzled, am i missing something.
You got the error because when you call _.each($scope.searchContents..., the data has not arrived from the async calls. addToSearchContents is not executed yet.
Use $q.all, which combines all promises into on giant one. And then do something after all promises are resolved.
Note: remember to inject the service $q to your controller.
$q.all([
contentService.getAllSongs(),
contentService.getAllPeople()
]).then(function (result) {
// `result` is an array containing the results from the promises.
var filteredSongs = filterList(result[0].data.songs, $scope.searchKeyword);
addToSearchContents(filteredSongs,"song");
var filteredPeople = filterList(result[1].data.people, $scope.searchKeyword);
addToSearchContents(filteredPeople,"people");
_.each($scope.searchContents,function(item){
alert("item -> "+item.type);
});
});
I do not have your coding context, so a similar JSFiddle is created for you. It illustrates the same idea.
Yet another variant, also with $q.all:
$q.all([
// get all songs
contentService.getAllSongs().then(function (result) {
var filteredSongs = filterList(result.data.songs, $scope.searchKeyword);
addToSearchContents(filteredSongs,"song");
}),
// get all people
contentService.getAllPeople().then(function (result) {
var filteredPeople = filterList(result.data.people, $scope.searchKeyword);
addToSearchContents(filteredPeople,"people");
})]).then(function(){
// here all items already added so we can iterate it
_.each($scope.searchContents,function(item){
alert("item -> "+item.type);
});
});
Related
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);
I want to create an array containing some objects
Firstly, I get a first array from the server containing a list of devices like this
[
{accountID : "sysadmin",deviceID : "123"},
{accountID : "sysadmin",deviceID : "3"}
...
]
Then I create a second array containing some objects that each object represent a device(deviceID) and contains an array of events of this device that I get from the server
I do a loop upon the first array like this :
$scope.myArrayofDevices = [];
angular.forEach(response, function(device){
$scope.myObject={};
$scope.myObject.device = device.deviceID;
$http.get('events')
.success(function (data) {
$scope.myObject.events = data;
});
$scope.myArrayofDevices.push($scope.myObject);
});//end for loop
I get events data from the server correctly .
But, when I check $scope.myArrayofDevices array I get an the first object with only the deviceID and no event array and the second object with deviceID and events array correctly
like this :
[
{deviceID : 123, events:},
{deviceID : 3 , events : array[5]}
]
How can I solve this issue ?
Note that I try to assign an array to $scope.myObject.events it works perfectly the problem is using a loop with $http
You can use $q.all() to resolve an array of promises and get the final result
angular.module('app', []);
angular.module('app').controller('ExampleController', ['$scope', '$q', function($scope, $q) {
$scope.myArrayofDevices = [];
$scope.getDeviceObject = function(deviceId) {
return $http.get('events/' + deviceId).then(function(deviceEvents) {
return {
"device": deviceId,
"events": deviceEvents
};
});
}
var promises = [];
angular.forEach(response, function(device) {
promises.push($scope.getDeviceObject(device.deviceID));
});
/*
* Combines multiple promises into a single promise
* that will be resolved when all of the input promises are resolved
*/
$q.all(promises).then(function(devices) {
$scope.myArrayofDevices = $scope.myArrayofDevices.concat(devices);
});
}]);
First of all: like Carnaru Valentin said, you should create a service to wrap your $http calls.
Secondly, I don't get your $http.get('events') call. You don't pass any parameters to it (deviceID or whatnot).
Does it return a list of all events for every devices ? For a specific device ?
If you just forgot to add a parameter to the query: here is a solution that could work:
var promises = response.map(function (device) {
return $http.get('events/' + device.deviceID)
.then(function (data) {
return {
device: device.deviceID,
events: data
};
});
})
$q.all(promises)
.then(function (devices) {
$scope.myArrayofDevices = $scope.myArrayofDevices.concat(devices);
// alternatively: $scope.myArrayofDevices = devices;
});
Thing is you reassign $scope.myObject to a new object before callback is fired and assigned events to the old one. So both callback's assign properties to same object.
You could just put all of the code in callback.
1. Create a service:
function DataService($http, appEndpoint){
return {
getLists: getData
}
function getData(){
return $http.get(appEndpoint + 'lists')
}
}
2. Create controller:
function ListsController(DataService){
var self = this;
self.data = null;
DataService.then(function(response){
self.data = response.data;
});
// Here manipulate response -> self.data;
}
myData is generated in myFactory, and the following code works if myData is generated from a single myHttp.GetGroupMember function.
However, it is not working with multiple GetGroupMember calls as they execute "at the same time", and dataContainer gets initialized from createMyData call.
How can I call second GetGroupMember after first GetGroupMember is "finished"?
var myFactory = function (myHttp) {
var myData = [];
var dataContainer = [];
var numProgress;
var onSuccess = function(data_member) {
myData.push(data_member);
numProgress++;
loadMember(dataContainer); // if member is succefully loaded, load next member
// if numProgress == data_member.length, call next GetGroupMember() here?
}
var loadMember = function(data_group) {
if (numProgress < data_group.length) {
myHttp.getMemberDetails(data_group.division, data_group.members[numProgress]).then(onSuccess, onError);
}
}
var createMyData = function(response) {
dataContainer = response; // coded as shown to call loadMember() iteratively
loadMember(dataContainer);
}
var getMyData = function(init) {
myHttp.getGroupMember("Division1", "Group_Foo").success(createMyData); // getGroupMember_1
// how to call getGroupMember_2 after getGroupMember "finished"?
myHttp.getGroupMember("Division2", "Group_Bar").success(createMyData); // getGroupMember_2
return myData;
}
}
var myControl = function($scope, myFactory) {
$scope.myData = myFactory.getMyData();
}
Easiest thing in the world:
myHttp.getGroupMember("Division1", "Group_Foo")
.success(createMyData)
.then(function() {
return myHttp.getGroupMember("Division2", "Group_Bar")
}).success(createMyData);
Read up on promises.
Solved: The below code now works for anyone who needs it.
Create an Angular factory that queries a database and returns query results.
Pass those results of the query into the $scope of my controller
Render the results of the $scope variable in my view (html)
Challenge:
Because this is for a node-webkit (nwjs) app, I am trying to do this without using express, setting up api endpoints, and using the $http service to return the query results. I feel like there should be a more eloquent way to do this by directly passing the data from nodejs to an angular controller. Below is what I've attempted but it hasn't worked.
Database: Below code is for a nedb database.
My Updated Controllers
app.controller('homeCtrl', function($scope,homeFactory){
homeFactory.getTitles().then(function(data){
$scope.movieTitles = data;
});
});
My Updated Factory:
app.factory('homeFactory', function($http,$q) {
return {
getTitles: function () {
var deferred = $q.defer();
var allTitles = [];
db.find({}, function (err, data) {
for (var i = 0, len = data.length; i < len; i++) {
allTitles.push(data[i].title)
}
deferred.resolve(allTitles);
});
return deferred.promise;
}
}
});
HTML
<script>
var Datastore = require('nedb');
var db = new Datastore({ filename: './model/movies.db', autoload: true });
</script>
<--shows up as an empty array-->
<p ng-repeat="movie in movieTitles">{{movie}}</p>
So the problem with your original homeFactory is that db.find happens asynchronously. Although you can use it just fine with callbacks, I think it is better practice to wrap these with $q.
app.service('db', function ($q) {
// ...
this.find = function (query) {
var deferred = $q.defer();
db.find(query, function (err, docs) {
if (err) {
deferred.reject(err);
} else {
deferred.resolve(docs);
}
});
return deferred.promise;
};
// ...
});
Once you begin wrapping non-Angular code like this, then it makes the interface consistent.
app.service('homeFactory', function (db) {
this.getAllTitles = function () {
return db.find({}).then(function (docs) {
return docs.map(function (doc) {
return doc.title;
});
});
};
});
And the controller would look like:
app.controller('homeCtrl', function ($scope, homeFactory) {
homeFactory.getAllTitles().then(function (titles) {
$scope.movieTitles = titles;
});
});
Partial Answer: I was able to get it working by moving all the code into the controller. Anyone now how to refactor the below to use a factory to clean up the code?
Updated Controller ($scope.movietitles is updating in views with the correct data):
$scope.movieTitles = [];
$scope.getMovieTitles = db.find({},function(err,data){
var arr = [];
for (var i = 0, len = data.length; i < len; i++){
arr.push(data[i].title)
}
$scope.movieTitles = arr;
$scope.$apply();
});
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();
}