It's really easy to have select options translated with angular-translate:
<select name="languageId"
ng-options="p.id as ('LANGUAGE.'+p.id)|translate for p in Const.languages | orderBy:'name'">
But that way the options are sorted by their original key not their translated one.
Is there a way I can have that list ordered by their translated value without preparing that list ahead in the controller?
The easiest way that worked for me (angular 1.2.24 and angular-translate 2.14.0):
<select name="nationality" ng-model="person.nationality" ng-options="c.id as c.name | translate for c in Const.countries | orderBy:'name | translate' ">
The credit for this goes to the writer of this comment: https://github.com/angular-translate/angular-translate/issues/1064#issuecomment-267357441
You could supply a predicate function to orderBy and have that function return the translated value. It means you also have to pass in the $filter service to your controller because you'd need to invoke translate inside your JS code.
So, to offer some code as guidance:
// CONTROLLER
// * Don't forget to inject the $filter service
$scope.translated = function(p) {
return $filter('translate')('LANGUAGE.' + p.id);
}
And then you'd modify your orderBy expression:
... | orderBy:translated
I realise the suggestion seems somewhat convoluted because one translation attempt occurs within ng-options and then another in orderBy, but it should sort the select options as you'd expect.
I had the same problem and I recommend using an own filter, because you don't want to mess up your controller! This is my current solution:
/**
* orderByTranslated Filter
* Sort ng-options or ng-repeat by translated values
* #example
* ng-options="country as ('countries.'+country | translate) for country in countries | orderByTranslated:'countries.'"
* #param {Array} array
* #param {String} i18nKeyPrefix
* #param {String} objKey
* #return {Array}
*/
app.filter('orderByTranslated', ['$translate', '$filter', function($translate, $filter) {
return function(array, i18nKeyPrefix, objKey) {
var result = [];
var translated = [];
angular.forEach(array, function(value) {
var i18nKeySuffix = objKey ? value[objKey] : value;
translated.push({
key: value,
label: $translate.instant(i18nKeyPrefix + i18nKeySuffix)
});
});
angular.forEach($filter('orderBy')(translated, 'label'), function(sortedObject) {
result.push(sortedObject.key);
});
return result;
};
}]);
Notice you need to pass your i18n prefix 'LANGUAGE.' and I saw your using an object instead of a simple string array so you can use it like this:
<select name="languageId"
ng-options="p.id as ('LANGUAGE.'+p.id)|translate for p in Const.languages | orderByTranslated:'LANGUAGE.':'id'">
I know it's an old question, but stumbled upon this today and here's how in this case you could solve it (only works for Array though):
<select name="languageId"
ng-options="p.id as name=('LANGUAGE.'+p.id)|translate for p in Const.languages | orderBy:'name'">
The trick is to assign the translation and use this assignment to sort on.
Related
I have a very simple piece of code to run a dynamic sortBy over my array. I am using a select with ng-model to return the correct key by which to sort. However, I can change the select once, and the orderBy works. But once I do it again, I get a very strange error
Controller
//change task sort
$scope.changeOrder = 'task_date';
$scope.changeOrder = (filterTask) => {
if (filterTask == "due") {
$scope.changeOrder = 'task_date';
} else if (filterTask == "imp") {
$scope.changeOrder = 'task_importence';
}
};
Template
<select ng-change=changeOrder(filterTask) ng-model="filterTask">
<option value="due">Due First</option>
<option value="imp">Importance</option>
</select>
<task-item ng-repeat="task in $ctrl.user.task | orderBy : changeOrder"></task-item>
Here is the error - There is nothing called "v2" in my system
Welcome to the untyped world that is JavaScript.
Your error is actually quite apparent: $scope.changeOrder becomes a function and a standard variable. Once you select a value in your select drop-down, it ceases to be a function and reverts to a standard variable. Then, you can no longer call it.
You would be wise to split this up into two variables instead. I'd recommend using $scope.orderState and $scope.changeOrder, where orderState just holds the strings and changeOrder is your function.
I think the problem is that both of your $scope variables have the same name. You try to assign a function and a value to $scope.changeOrder. Try splitting it up into two variables
I am looking to order my list by a number of specific values on one property, and then some addtional properties once that is done.
The code I have is:
<tr ng-repeat-start="subcontractor in trade.SubContractors | orderBy:['Status':'PREF','Status':'APPR','Status':'LOGD','Status':'NIU','-AverageScores.AverageScore','-ProjectCount','SubcontractorName']">
Where the important bit (and the bit I can't get working) is:
'Status':'PREF','Status':'APPR','Status':'LOGD','Status':'NIU'
Is there a way of doing this in angular?
I would instead implement a handler to process data. Process subcontractors before it's set, running each element through a handler and assigning "sortValue" property to each one.
Then simply call orderBy using the sortValue property. This way you would decouple sorting data from displaying data. Though don't use a filter to do so, as it would be quite expensive resource-vise.
Something like
var statusSorting = ['PREF','APPR','LOGD','NIU'];
function sortContractors(models) {
var processed = [];
angular.forEach(models, function(model){
// logic to assign sortValue
var statusIndex = statusSorting.indexOf(model.status);
model.sortValue = statusIndex + 1;
});
return processed;
}
api.getData()
.then(function(data){
$scope.models = sortContractors(data);
});
// template
<tr ng-repeat="model in models | orderBy:'sortValue'">
You can then control priority by changing status position in the array and ordering desc/asc.
Alternately:
orderBy multiple fields in Angular
<tr ng-repeat="model in models | orderBy:['sortValue', 'contractorName']">
Your answer had a few non-required parts so I've tweaked it to give me the following:
function getStatusSortOrder(subcontractors) {
var statusSorting = ['PREF', 'APPR', 'LOGD', 'NIU'];
angular.forEach(subcontractors, function (model) {
statusIndex = statusSorting.indexOf(model.Status);
model.StatusSortValue = statusIndex;
});
}
I've been trying to make a list of geozones, with a select of taxes each (not all taxes apply to all geozones).
So I did a ng-repeat for the geozones, and inside each of them a ng-repeat with all taxes. Problem is I don't know how to send the id of the geozone being filtered at the moment. This is the code right now:
<md-option ng-repeat="tax in taxClasses | filter: filterTax" value="{{ '{{ tax.id }}' }}">{{ '{{ tax.name }}' }}</md-option>
and the JS:
$scope.filterTax = function(tax, n){
angular.forEach(tax.geoZone , function(geo){
if(geo === $scope.prices[n].geozoneId){
return true;
}
});
return false;
};
Need n to be the index of the geozone, or something of the sort. Thanks in advance!
Your idea is not that far off, but using filter: is not even necessary, as the pipe | is already a filter command:
ng-repeat="<var> in <array> | <filterFunction>:<...arguments>"
Thus you can create a filter (see https://docs.angularjs.org/guide/filter for details on that)
ng-repeat="tax in taxClasses | filterTax: <geozoneIndex>"
The value form the collection will be passed as the first argument of your filterTax function. Any further argument is passed separated by a colon :.
When you use this, you have to propagate a filter method like this:
app.module(...).filter('filterTax', function(/* injected services */) {
return function (/* arguments */ input, geozoneIndex) {
// function body
}
});
Alternatively use a filter function from your scope:
// template
ng-repeat="tax in filterTaxes(taxClasses, <geozoneIndex>)"
// script
$scope.filterTaxes = function(taxClasses, geozoneIndex) {
// do the whole filtering, but return an array of matches
return taxClasses.filter(function(taxClass) {
return /* in or out code */;
});
};
This means your geozoneIndex may be either a fixed value or being pulled from a variable, that's available in that scope.
Just be aware that your filterTax function will be called a lot, so if your page is getting slow you might want to consider optimizing that filtering.
Can anyone tell me why the model option in the first example is selected, and the second one does not for a plain array:
// Plain old array
vm.owner = ['example', 'example2', 'example3', ...];
Where the model vm.model.address.owner = 2;
// EXAMPLE 1 - Works
// Force index to be a number: using id*1 instead of it being a string:
// and the option ('example3') is selected based on the model value of 2
// indicating the index
<select id="owner"
name="owner"
placeholder="Residential Status"
ng-model="vm.model.address.owner"
ng-required="true"
ng-options="id*1 as owner for (id, owner) in vm.owner">
<option value="">Select Value</option>
</select>
Attempting to not use a hack and using track by instead index 2 is not selected even though the value is still set in the model.
// EXAMPLE 2 - Doesn't Work
// Markup doesn't show value to be a string: using track by, but the
// option set in the model doesn't cause the option to be selected it
// remains as the default with a value of ''
<select id="owner"
name="owner"
placeholder="Residential Status"
ng-model="vm.model.address.owner"
ng-required="true"
ng-options="owner for (id, owner) in vm.owner track by id">
<option value="">Select Value</option>
</select>
I find ngOptions to be super confusing so any explanation or solution for example 2 since it is cleaner and not a hack would be great.
Didn't find a solution using track by, but the AngularJS docs for Select had a solution using a parser and formatter so I could get away from using the hack in the question. I tweaked it a bit so if the key was a string it will leave it alone, otherwise it converts it, and this seems to work. Any criticisms or issues that I don't see please comment, otherwise hope this helps someone.
(function () {
'use strict';
/**
* Binds a select field to a non-string value via ngModel parsing and formatting,
* which simply uses these pipelines to convert the string value.
* #constructor
* #ngInject
* ---
* NOTE: In general matches between a model and an option is evaluated by strict
* comparison of the model value against the value of the available options.
* Setting the option value with the option's "value" attribute the value
* will always be a "string", which means that the model value must also
* be a string, otherwise the select directive cannot match them
* reliably.
*/
function selectConvertKey(_) {
return {
require: 'ngModel',
link: function ($scope, $element, $attrs, $ctrl) {
var ngModelCtrl = $ctrl;
// Do nothing if no ng-model
if (!ngModelCtrl) {
return;
}
// ---
// PRIVATE METHODS.
// ---
/**
* Convert the key to a number if the key is a number.
* #param key
* #returns {Number}
* ---
* NOTE: Using Number() instead of parseInt() means that a string
* composed of letters and numbers, and start with a number will
* not be converted.
*/
function selectConvertKeyParser(key) {
var keyAsNumber = Number(key);
// Check if the key is not a number
if(_.isNaN(keyAsNumber)) {
return key;
}
return keyAsNumber;
}
/**
* Convert the key to a string.
* #param key
* #returns {string}
*/
function selectConvertKeyFormatter(key) {
return '' + key;
}
// ---
// MODEL PROPERTIES.
// ---
/**
* Formatters used to control how the model changes are formatted
* in the view, also known as model-to-view conversion.
* ---
* NOTE: Formatters are not invoked when the model is changed
* in the view. They are only triggered if the model changes
* in code. So you could type forever into the input, and
* the formatter would never be invoked.
*/
ngModelCtrl.$formatters.push(selectConvertKeyFormatter);
/**
* Parsers used to control how the view changes read from the
* DOM are sanitized/formatted prior to saving them to the
* model, and updating the view if required.
*/
ngModelCtrl.$parsers.push(selectConvertKeyParser);
}
};
}
selectConvertKey.$inject = [
'_'
];
angular
.module('app')
.directive('selectConvertKey', selectConvertKey);
})();
Yes, the problem appears to be that the select is being bound to a non-string value. If you do the following, it would work:
//controller
vm.model.address.owner = "2"
//html
ng-options="id as owner for (id, owner) in vm.owner"
See Angularjs ng-options using number for model does not select initial value.
Also, if you want to leave the model value as a number (2, not "2") you can try this:
ng-options="vm.owner.indexOf(owner) as owner for (id, owner) in vm.owner"
However, that may not be any less "hackish" than your working first example:
ng-options="id*1 as owner for (id, owner) in vm.owner">
See the first answer at AngularJS ng-option get index.
I'm using ng-repeat to create a list of entires and using a filter.
Is it possible to get the number of entries returned, like a length
data-ng-repeat="entry in entries | filter: { country_code : countryCode }"
It is little bit tricky, use ng-init:
ng-init="filter_len = (entries | filter: { country_code : countryCode }).length"
Example: http://jsfiddle.net/KJ3Nx/
As you know filter responsible to "filter" the input list and it returns filtered list where objects have the same structure. Otherwise you get digest cycle reentering that causes Exceptions (aka > 10 cycles).
1st way
Get length after filtering:
<pre>{{(entries| myfilter:types).length }}</pre>
See Example
2nd way
Use custom filter and get length from there.
iApp.filter('myfilter', function() {
return function( entries) {
var filtered = [];
angular.forEach(entries, function(entry) {
filtered.push(entry);
});
// here fetch list length
return filtered;
};
});
I suppose the following code will work for you:
<p>Filtered number: {{(entries|filter:{country_code:countryCode}).length}}</p>
<p>Total number: {{entries.length}}</p>