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;
}
Related
I'm working with mongodb stitch/realm and I'm trying to modify objects inside an array with a foreach and also pushing ids into a new array.
For each object that i'm modifying, I'm also doing a query first, after the document is found I start modifying the object and then pushing the id into another array so I can use both arrays later.
The code is something like this:
exports = function(orgLoc_id, data){
var HttpStatus = require('http-status-codes');
// Access DB
const db_name = context.values.get("database").name;
const db = context.services.get("mongodb-atlas").db(db_name);
const orgLocPickupPointCollection = db.collection("organizations.pickup_points");
const orgLocStreamsCollection = db.collection("organizations.streams");
const streamsCollection = db.collection("streams");
let stream_ids = [];
data.forEach(function(stream) {
return streamsCollection.findOne({_id: stream.stream_id}, {type: 1, sizes: 1}).then(res => { //if I comment this query it will push without any problem
if(res) {
let newId = new BSON.ObjectId();
stream._id = newId;
stream.location_id = orgLoc_id;
stream.stream_type = res.type;
stream.unit_price = res.sizes[0].unit_price_dropoff;
stream._created = new Date();
stream._modified = new Date();
stream._active = true;
stream_ids.push(newId);
}
})
})
console.log('stream ids: ' + stream_ids);
//TODO
};
But when I try to log 'stream_ids' it's empty and nothing is shown. Properties stream_type and unit_price are not assigned.
I've tried promises but I haven't had success
It's an asynchronous issue. You're populating the value of the array inside a callback. But because of the nature of the event loop, it's impossible that any of the callbacks will have been called by the time the console.log is executed.
You mentioned a solution involving promises, and that's probably the right tack. For example something like the following:
exports = function(orgLoc_id, data) {
// ...
let stream_ids = [];
const promises = data.map(function(stream) {
return streamsCollection.findOne({ _id: stream.stream_id }, { type: 1, sizes: 1 })
.then(res => { //if I comment this query it will push without any problem
if (res) {
let newId = new BSON.ObjectId();
// ...
stream_ids.push(newId);
}
})
})
Promise.all(promises).then(function() {
console.log('stream ids: ' + stream_ids);
//TODO
// any code that needs access to stream_ids should be in here...
});
};
Note the change of forEach to map...that way you're getting an array of all the Promises (I'm assuming your findOne is returning a promise because of the .then).
Then you use a Promise.all to wait for all the promises to resolve, and then you should have your array.
Side note: A more elegant solution would involve returning newId inside your .then. In that case Promise.all will actually resolve with an array of the results of all the promises, which would be the values of newId.
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);
Factory that gets the rates using REST
.factory('rateResource', ['$resource', function ($resource) {
return $resource('/rates/:currency/:effectiveDate', {
currency: '#currency',
effectiveDate: '#effectiveDate'
});
}])
Service that calls the factory to get resource and retrieve the rates and also errors if there are any.
.service('RateService', ['rateResource', '$rootScope',
function (rateResource, $rootScope) {
var self = this;
self.rates = [];
self.search = function (baseCurrency, effectiveDate) {
self.rates = [];
var error = null;
self.baseCurrency = baseCurrency;
self.effectiveDate = effectiveDate;
if (baseCurrency) {
rateResource.query({currency: baseCurrency, effectiveDate: effectiveDate})
.$promise.then(function (rates) {
if (rates) {
angular.forEach(rates, function (rate) {
rate.maintTs = $rootScope.formatTimestampToHHMMSS(rate.maintTs);
rate.editable = false;
self.rates.push(rate);
});
self.processing = false;
}
}, function (response) {
self.processing = false;
error = 'Processing failed due to '+response.status;
console.log(error);
})
return {
rates: self.rates,
errors: error
}
}
}
}]);
Controller calls the service for rates.
.controller('RateController', ['RateService',
function (rateService) {
var self = this;
self.baseCurrency = rateService.getBaseCurrency();
self.effectiveDate = rateService.getEffectiveDate();
self.rates = rateService.getRates();
//make the call from the controller
self.search = function () {
var response = rateService.search(self.baseCurrency, self.effectiveDate.yyyyMMdd());
self.rateRecords = response.rates;
self.errors = response.errors;
}
}])
rates are showing up fine in the controller after the promise is fulfilled. However, upon receiving errors, they are not getting transferred from service to controller. ( I changed the REST URL to make service return a 404 response ). What am I doing wrong?
Currently you are returning asynchronous call response from outside of async function, you shouldn't do that technically. Asynchronous code always get there responses inside their promise/callback function.
But in your case it is working because you are returning object with it reference. If you look at below code, return statement has carried self.rates object reference, so while returning even if it is blank, it is going to get updated value once self.rates gets filled up. So then you don't need to worry about the rates updation. But same thing for error would not work because it is of primitive datatype like var error = null, so when you are returning value it would be null(as async response haven't completed).
return {
rates: self.rates, //passed by reference
errors: error //passed by primitive type
}
So for solving this issue you could also make the error to an object type, so that it reference will get passed with reference object, like var errors = [] & when error occur push the error message into that array using .push
But I'd not recommend above way to go for, I'd rather take use of promise pattern and will maintain proper code call stack. Basically for that you need to return promise from search method & then put .then function to wait till resolve that function.
Service
.service('RateService', ['rateResource', '$rootScope',
function(rateResource, $rootScope) {
var self = this;
self.rates = [];
self.search = function(baseCurrency, effectiveDate) {
self.rates = [];
var error = null;
self.baseCurrency = baseCurrency;
self.effectiveDate = effectiveDate;
if (baseCurrency) {
return rateResource.query({
currency: baseCurrency,
effectiveDate: effectiveDate
})
.$promise.then(function(rates) {
if (rates) {
angular.forEach(rates, function(rate) {
rate.maintTs = $rootScope.formatTimestampToHHMMSS(rate.maintTs);
rate.editable = false;
self.rates.push(rate);
});
self.processing = false;
}
//returning object from promise
return {
rates: self.rates,
errors: error
}
}, function(response) {
self.processing = false;
error = 'Processing failed due to ' + response.status;
console.log(error);
//returning object from promise
return {
rates: self.rates,
errors: error
}
})
}
}
}
])
Controller
//make the call from the controller
self.search = function() {
var response = rateService.search(self.baseCurrency, self.effectiveDate.yyyyMMdd()).then(function() {
self.rateRecords = response.rates;
self.errors = response.errors;
});
}
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);
});
});
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]];
}