angular-translate inside loop - angularjs

I'm trying to construct a translated message by looping over an array of objects and then adding a new "message" property to that object containing the translated string. I see the correct message output while inside $translate.then(); but when I assign the message to the object it is undefined. What is the correct way to resolve the promise returned from $translate.then() and assign it to the "message" property?
//items.controller.js
function getItems() {
return itemsFactory.getItems()
.then(function (response) {
vm.items = initItemsList(response.activities);
});
}
function initItemsList(itemsList) {
for (var i = 0; i < itemsList.length; i++){
var activityType = itemsList[i].activityType;
switch (activityType){
case "HISTORY": {
var itemName = itemsList[i].item.itemName;
var itemVersion = itemsList[i].item.itemVersion;
$translate('activity.'+activityType, { itemname: itemName, itemversion: itemVersion }).then(function(content){
vm.itemContent = content;
console.log(vm.itemContent); // correct message displayed.
});
break;
}
default: {
break;
}
}
itemsList[i].message = vm.itemContent; // undefined
}
return itemsList;
}
// translation.json
"activity : {
"HISTORY" : "History for {{ itemname }} {{ itemversion }}."
}

Promises are always resolved asynchronously. So the statement
itemsList[i].message = vm.itemContent;
, which is executed right after the switch, is executed before the callback passed to the $translate promise. Just move the statement to the callback:
$translate('activity.'+activityType, { itemname: itemName, itemversion: itemVersion }).then(function(content){
vm.itemContent = content;
console.log(vm.itemContent);
itemsList[i].message = vm.itemContent;
});

As #Vegar correctly states, the code inside then is executed after the assignment so moving the assignment inside then function will take care of the problem. However, your itemsList will be returned from the function before all the translations are done so you will need to return a promise that resolves when all translations are done:
function initItemsList(itemsList) {
var allTranslations = [];
for (var i = 0; i < itemsList.length; i++){
var activityType = itemsList[i].activityType;
switch (activityType){
case "HISTORY": {
var itemName = itemsList[i].item.itemName;
var itemVersion = itemsList[i].item.itemVersion;
allTranslations.push($translate('activity.'+activityType, { itemname: itemName, itemversion: itemVersion }).then(function(content){
vm.itemContent = content;
itemsList[i].message = vm.itemContent;
}));
break;
}
default: {
break;
}
}
}
return $q.all(allTranslations);
}
The caller of your function will have to do like:
initItemList(itemList).then(function(translatedList){
//Do stuff with translated list
});

Related

knockout push item to non-existing observableArray

From within a viewmodel I'm trying to dynamically push items to an observableArray. The ajax returns the data correctly.
html :
<li class="liTagulTagsChild" data-bind="click:$root.GetEmissions">/li>
javascript:
var TagDetail = function (di_titre, di_diffusion) {
this.di_titre = ko.observable(di_titre);
this.di_diffusion = ko.observable(di_diffusion);
}
var testA = [];
this.test = ko.observableArray(testA);
this.GetEmissions = function (c, event) {
var element = event.target;
var tag_id = element.getAttribute('tag_id');
$.AjaxAntiforgery({
url: 'Emission/GetDetailsByTagID/',
data: {
tag_id: tag_id
},
success: function (result) {
for (var i = 0; i < result.length; i++) {
var tD = new TagDetail(result[i].DI_TITRE, result[i].DI_DIFFUSION);
this.test.push(tD);
}
}
});
}
Problem: From within the GetEmissions function I cannot push items to this.test because this.test is null (not defined).
In other words,
this.test.push(tD);
fails. (null reference or not defined)
A solution anyone ?
In Javascript "this" doesn't mean what you think it means. Check out How to access the correct `this` context inside a callback?

Angular nested Promise shows an error

I have 2 API calls.
The second API call depends on the Property ID returned to make the second API call to check if each of these properties has parking.
If it does, then I add details of that property to an object and push the object into an Array.
The second API call is nested inside the first. After I've looped through all the properties, I check if the Array length is more than 0, if it is then I can display the returned properties in page, else it shows an error.
The problem is even when there are properties returned with parking, the else statement or error function executes, as well as displaying properties on the page.
Is there a way to complete the nested Promise before checking if my Array is more than 0?
Here's my code:
$scope.viewPropertyList = function(latlong) {
$scope.locationError = false;
var latlongArray = latlog.split('::');
var searchLat_scope = latlongArray[0];
var searchLon_scope = latlongArray[1];
if (searchLat_scope && searchLon_scope) {
var data = Property.getAllProperties({
dest: 'property',
apikey: API_KEY,
lat: encodeURIComponent(searchLat_scope),
limit: 10,
lon: encodeURIComponent(searchLon_scope)
}).$promise.then(function(success) {
var propertyMarkers = [];
$scope.dbMarkers = 0;
for (var i = 0, l = success.property.length; i < l; i++) {
(function(i) {
Property.getProperty({
dest: 'property',
propertyId: success.property[i].name,
apikey: API_KEY
}).$promise.then(function(propertyData) {
for (var j = 0, k = propertyData.services.length; j < k; j++) {
if (propertyData.services[j].name === "parking") {
var obj = {
"propertyName": success.property[i].propertyName,
"telephone": success.property[i].telephone,
"postcode": success.property[i].address.postcode,
"city": success.property[i].address.city,
"county": success.property[i].address.county,
"addressLine1": success.property[i].address.addressLine1
};
propertyMarkers.push(obj);
}
}
if (propertyMarkers.length != 0) {
$scope.dbMarkers = propertyMarkers;
$scope.selectedLat = searchLat_scope;
$scope.selectedlog = searchLon_scope;
} else {
$scope.locationErr = true;
$scope.errorMsg = "No properties found";
}
});
})(i);
}
}, function(error) {
$scope.locationErr = true;
$scope.errorMsg = "Something went wrong, please try again";
});
}
}
Two main things :
there's no attempt to aggregate multiple promises generated in a loop.
the if (propertyMarkers.length > 0) {...} else {...} is too deeply nested.
Minor :
the inner iteration can break as soon as 'parking' is found. If it continued and further 'parking' was found, then duplicate markers would be created.
$scope.viewPropertyList = function(latlong) {
$scope.locationError = false;
var latlongArray = latlog.split('::');
var searchLat_scope = latlongArray[0];
var searchLon_scope = latlongArray[1];
if (searchLat_scope && searchLon_scope) {
Property.getAllProperties({
dest: 'property',
apikey: API_KEY,
limit: 10,
lat: encodeURIComponent(searchLat_scope),
lon: encodeURIComponent(searchLon_scope)
}).$promise.then(function(success) {
var propertyMarkers = [];
$scope.dbMarkers = 0;
// create an array of promises by mapping the array `success.property`.
var promises = success.property.map(function(prop) {
return Property.getProperty({
dest: 'property',
propertyId: prop.name,
apikey: API_KEY
}).$promise.then(function(propertyData) {
for (var j=0, k=propertyData.services.length; j<k; j++) {
if (propertyData.services[j].name === 'parking') {
propertyMarkers.push({
'propertyName': prop.propertyName,
'telephone': prop.telephone,
'postcode': prop.address.postcode,
'city': prop.address.city,
'county': prop.address.county,
'addressLine1': prop.address.addressLine1
});
break; // 'parking' is found - no point iterating further
}
}
});
});
/* ******** */
// Aggregate `promises`
$q.all(promises).then(function() {
// This block is now un-nested from its original position,
// and will execute when all `promises` have resolved.
if (propertyMarkers.length > 0) {
$scope.dbMarkers = propertyMarkers;
$scope.selectedLat = searchLat_scope;
$scope.selectedlog = searchLon_scope;
} else {
$scope.locationErr = true;
$scope.errorMsg = 'No parking found';
}
});
/* ******** */
}).catch(function(error) {
$scope.locationErr = true;
$scope.errorMsg = 'Something went wrong, please try again';
});
} else {
$scope.locationErr = true;
$scope.errorMsg = 'Problem with lat/lng data';
}
}
Notes :
that the outer iteration is now coded as success.property.map(), which returns promises and avoids the need for an IIFE.
Extra error handling added
If I got your problem right, you want all Property.getProperty promises of success.property are resolved before going to the success function to check propertyMarkers length.
In that case, you need $q.all to resolve all the Property.getProperty promises for you.
In your for (var i = 0, l = success.property.length; i < l; i++) { appends all the promises into an array
Property.getProperty({
dest: 'property',
propertyId: success.property[i].name,
apikey: API_KEY
})
then use $q.all(arrPromises).then(function(propertyData) { to do the following check.
One more thing worth to mention, promise chaining can be easily achieved by $promise.then(successFn, failFn).then(successFn, failFn).then.... Every time you call then() will create another promise which allows you to chain to next promise and pass value to the next.

AngularJS - create object array where label is $translated

I have the following array:
vm.roles = ['ROLE1', 'ROLE2', 'ROLE3', 'ROLE4'];
and I need this form of array:
vm.translatedRoles = [{id:0, label:'Role1'}, {id:1, label:'Role2'}, ...]
Therefore I wrote this function to transfer from vm.roles to vm.translatedRoles.
My Problem now is that translatedRoles stays empty, so there are no objects in it. Does anyone know why?
function translateRoles() {
var translatedRoles = [];
for(var i = 0; i < vm.roles.length; i++) {
$translate(vm.roles[i]).then(function(text) {
var role = {};
role.id = i;
role.label = text;
translatedRoles.push(role);
});
}
return translatedRoles;
}
That can't work. $translate() returns a promise. The function passed to $translate is executed later, asynchronously, when the translations are available. So, the return statement happens before translatedRoles is populated by the function.
You need to return a promise of array, or hope that the translations are already available and use $translate.instant():
function translateRoles() {
var translatedRoles = [];
for (var i = 0; i < vm.roles.length; i++) {
translatedRoles.push({
id: i,
label: $translate.instant(vm.roles[i]);
});
}
return translatedRoles;
}

checkbox filter for json array in Angularjs

I have create a filter but this filter is not working with array inside array.
'http://plnkr.co/edit/oygy79j3xyoGJmiPHm4g?p=info'
Above plkr link is working demo.
app.filter('checkboxFilter', function($parse) {
var cache = { //create an cache in the closure
result: [],
checkboxData: {}
};
function prepareGroups(checkboxData) {
var groupedSelections = {};
Object.keys(checkboxData).forEach(function(prop) {
//console.log(prop);
if (!checkboxData[prop]) {
return;
} //no need to create a function
var ar = prop.split('=');
//console.log("ar is - "+ar);
if (ar[1] === 'true') {
ar[1] = true;
} //catch booleans
if (ar[1] === 'false') {
ar[1] = false;
} //catch booleans
/* replacing 0 with true for show all offers */
if(ar[0]=='SplOfferAvailable.text'){
ar[1]='true';
}else{
}
//make sure the selection is there!
groupedSelections[ar[0]] = groupedSelections[ar[0]] || [];
//at the value to the group.
groupedSelections[ar[0]].push(ar[1]);
});
return groupedSelections;
}
function prepareChecks(checkboxData) {
var groupedSelections = prepareGroups(checkboxData);
var checks = [];
//console.log(groupedSelections);
Object.keys(groupedSelections).forEach(function(group) {
//console.log("groupedSelections- "+groupedSelections);
//console.log("group- "+group);
var needToInclude = function(item) {
//console.log("item- "+item);
// use the angular parser to get the data for the comparson out.
var itemValue = $parse(group)(item);
var valueArr = groupedSelections[group];
//console.log("valueArr- "+valueArr);
function checkValue(value) { //helper function
return value == itemValue;
}
//check if one of the values is included.
return valueArr.some(checkValue);
};
checks.push(needToInclude); //store the function for later use
});
return checks;
}
return function(input, checkboxData, purgeCache) {
if (!purgeCache) { //can I return a previous 'run'?
// is the request the same as before, and is there an result already?
if (angular.equals(checkboxData, cache.checkboxData) && cache.result.length) {
return cache.result; //Done!
}
}
cache.checkboxData = angular.copy(checkboxData);
var result = []; // this holds the results
//prepare the checking functions just once.
var checks = prepareChecks(checkboxData);
input.every(function(item) {
if (checks.every(function(check) {
return check(item);
})) {
result.push(item);
}
return result.length < 10000000; //max out at 100 results!
});
cache.result = result; //store in chache
return result;
};
});
above code is for check box filter.
when i click on checkbox called "Availability" it does not filter the result.
Please help me out.
Thanks.
I think that the way you are navigating through json is wrong because if you put in this way it works
"Location": "Riyadh",
"AvlStatus": "AVAILABLE"
"Rooms": {.....
You have to go in some way through Rooms and right now I think you're not doing that

Nodejs async data duplication

I'm having some problems with one async process on nodejs.
I'm getting some data from a remote JSON and adding it in my array, this JSON have some duplicated values, and I need check if it already exists on my array before add it to avoid data duplication.
My problem is when I start the loop between the JSON values, the loop call the next value before the latest one be process be finished, so, my array is filled with duplicated data instead of maintain only one item per type.
Look my current code:
BookRegistration.prototype.process_new_books_list = function(data, callback) {
var i = 0,
self = this;
_.each(data, function(book) {
i++;
console.log('\n\n ------------------------------------------------------------ \n\n');
console.log('BOOK: ' + book.volumeInfo.title);
self.process_author(book, function() { console.log('in author'); });
console.log('\n\n ------------------------------------------------------------');
if(i == data.length) callback();
})
}
BookRegistration.prototype.process_author = function(book, callback) {
if(book.volumeInfo.authors) {
var author = { name: book.volumeInfo.authors[0].toLowerCase() };
if(!this.in_array(this.authors, author)) {
this.authors.push(author);
callback();
}
}
}
BookRegistration.prototype.in_array = function(list, obj) {
for(i in list) { if(list[i] === obj) return true; }
return false;
}
The result is:
[{name: author1 }, {name: author2}, {name: author1}]
And I need:
[{name: author1 }, {name: author2}]
UPDATED:
The solution suggested by #Zub works fine with arrays, but not with sequelize and mysql database.
When I try to save my authors list on the database, the data is duplicated, because the system started to save another array element before finish to save the last one.
What is the correct pattern on this case?
My code using database is:
BookRegistration.prototype.process_author = function(book, callback) {
if(book.volumeInfo.authors) {
var author = { name: book.volumeInfo.authors[0].toLowerCase() };
var self = this;
models.Author.count({ where: { name: book.volumeInfo.authors[0].toLowerCase() }}).success(function(count) {
if(count < 1) {
models.Author.create(author).success(function(author) {
console.log('SALVANDO AUTHOR');
self.process_publisher({ book:book, author:author }, callback);
});
} else {
models.Author.find({where: { name: book.volumeInfo.authors[0].toLowerCase() }}).success(function(author) {
console.log('FIND AUTHOR');
self.process_publisher({ book:book, author:author }, callback);
});
}
});
// if(!this.in_array(this.authors, 'name', author)) {
// this.authors.push(author);
// console.log('AQUI NO AUTHOR');
// this.process_publisher(book, callback);
// }
}
}
How can I avoid data duplication in an async process?
This is because you are comparing different objects and result is always false.
Just for experiment type in the console:
var obj1 = {a:1};
var obj2 = {a:1};
obj1 == obj2; //false
When comparing objects (as well as arrays) it only results true when obj1 links to obj2:
var obj1 = {a:1};
var obj2 = obj1;
obj1 == obj2; //true
Since you create new author objects in each process_author call you always get false when comparing.
In your case the solution would be to compare name property for each book:
BookRegistration.prototype.in_array = function(list, obj) {
for(i in list) { if(list[i].name === obj.name) return true; }
return false;
}
EDIT (related to your comment question):
I would rewrite process_new_books_list method as follows:
BookRegistration.prototype.process_new_books_list = function(data, callback) {
var i = 0,
self = this;
(function nextBook() {
var book = data[i];
if (!book) {
callback();
return;
}
self.process_author(book, function() {
i++;
nextBook();
});
})();
}
In this case next process_author is being called not immediately (like with _.each), but after callback is executed, so you have consequence in your program.
Not sure is this works though.
Sorry for my English, I'm not a native English speaker

Resources