By default, if I try to translate a non-existent key using the $translate service or the translate filter, the key itself is returned.
e.g. $translate.instant('no.such.key') === 'no.such.key'
Is there a way to change this (per-invocation, rather than globally) so that null is returned instead?
Alternatively, does ng-translate provide a more concise way than this to show a translation only if it exists?
<div ng-if="('no.such.key' | translate) !== 'no.such.key')">
{{'no.such.key' | translate}}
</div>
There is a custom error handler where you can control the text that is displayed when the key cannot be found.
https://angular-translate.github.io/docs/#/guide/17_custom-error-handler
I copy and paste below the example from this page.
app.factory('customTranslationHandler', function () {
return function (translationID, uses) {
// return the following text as a translation 'result' - this will be
// displayed instead of the language key.
return 'NO DEFAULT KEY';
};
});
Maybe preferable for you would be to instead silently log missing keys. See https://angular-translate.github.io/docs/#/guide/16_error-handling
Best way to change this per invocation basis is to create a custom filter and translate inside of it.
$filter enables use of all filters within code so we can test the translate return and instead return null. This behavior doesn't override the default translate filter so you can use only where needed.
angular
.module('app')
.filter('translateNull', ['$filter', function($filter) {
return function(value) {
//Store translation to test and return
const tempValue = $filter("translate")(value);
//If translate returns the same key sent we return null
if (tempValue === value) {
return null;
} else {
return tempValue;
}
};
}]);
Source: https://docs.angularjs.org/tutorial/step_11
Related
For an Ember app, is it possible to use a constant as part of a computed property key ?
So, essentially, I have a constant as below;
MY_SECTION {
MY_FIELD: "my-field-id"
}
What I want is a computed property on "my-field-id" i.e.
myCP: function() {
console.log('Inside CP...');
}.property('section.field.my-field-id.value')
However, I want to be able to use constant for my-field-id instead of using it directly. Is that possible ?
Ola #testndtv, thanks for your question! Yes it is entirely possible to use a constant in the key for a computed property, but to make use of it you will need to use the more modern syntax that #jelhan was mentioning because .poperty() is deprecated.
Here is a working example of a controller that I have tested locally and is working as you would expect:
import Controller from '#ember/controller';
import { defineProperty, computed } from '#ember/object';
const PROPERTY_ID = 'some-random-string-that-is-too-long-to-write';
export default Controller.extend({
// this is just for the example so we can show the value in the template
// it is not needed to get this to work
PROPERTY_ID: PROPERTY_ID,
init() {
this._super(...arguments);
defineProperty(this, 'myCP', computed(PROPERTY_ID, function() {
return this.get(PROPERTY_ID);
}));
},
actions: {
addOne() {
// this is just for the example to stop the result always being NaN because
// null + 1 = NaN
let value = this.get(PROPERTY_ID) || 0;
this.set(PROPERTY_ID, value + 1);
}
}
});
As you can see we are making use of defineProperty which is being imported from '#ember/object'. You can read more about it in the API documentation
The key insight here is that you need to define the property dynamically in the init() for this Ember object.
The corresponding template for this Controller is as follows:
Property ID is: {{PROPERTY_ID}}
<br>
And the value is: {{get this PROPERTY_ID}}
<br>
<button {{action 'addOne'}}>Add One</button>
I am having trouble getting my custom filter to work.
I have a module of global filters:
angular.module('globalFilters', []).filter('dateRange', function () {
return function(item) {
console.log(item);
return true;
}
});
This is injected into my app on creation.
I am trying to apply this to an ng-repeat:
tr.pointer(ng-repeat="asset in completed | dateRange", ng-animate="'animate'", ng-click="selectAsset(asset);")
td {{asset.Name}}
However adding this filter will filter all assets out of table. To try to isolate the problem I am returning true for the function to display all assets but it is not working.
Upon logging item to the console, result appears to be an array of all assets so I guess something is wrong.
I was following this tutorial https://docs.angularjs.org/tutorial/step_09
Thanks!
You are filtering an array...so your filter function needs to return an array.
.filter('dateRange', function () {
return function(itemArray) {
if(!itemArray){
return null
}else{
return itemArray.filter(function(item){
// conditions of filter
});
}
}
});
When you define a custom filter function, the value passed into the filter is replaced by the value returned from the filter, so you are replacing item with true.
For a filter which logs the input without changing it, just return the input:
return function(item) {
console.log(item);
return item;
}
I use the following code which is working great but I wonder if in JS there is a way to avoid the if and to do it inside the loop, I want to use also lodash if it helps
for (provider in config.providers[0]) {
if (provider === "save") {
....
You can chain calls together using _.chain, filter by a value, and then use each to call a function for each filtered result. However, you have to add a final .value() call at the end for it to evaluate the expression you just built.
I'd argue that for short, simple conditional blocks, an if statement is easier and more readable. I'd use lodash- and more specifically chaining- if you are combining multiple operations or performing sophisticated filtering, sorting, etc. over an object or collection.
var providers = ['hello', 'world', 'save'];
_.chain(providers)
.filter(function(provider) {
return provider === 'save';
}).each(function(p) {
document.write(p); // your code here
}).value();
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.8.0/lodash.js"></script>
Edit: My mistake; filter does not have an overload where you can just supply a literal value. If you want to do literal value checking you have to supply a function as in my amended answer above.
I'd argue that what you have there is pretty good, clean and readable, but since you mentioned lodash, I will give it a try.
_.each(_.filter(config.providers[0], p => p === 'save'), p => {
// Do something with p
...
});
Note that the arrow function/lambda of ECMAScript 6 doesn't come to Chrome until version 45.
Basically, you are testing to see if config.providers[0], which is an object, contains a property called save (or some other dynamic value, I'm using a variable called provider to store that value in my example code below).
You can use this instead of using a for .. in .. loop:
var provider = 'save';
if (config.providers[0][provider] !== undefined) {
...
}
Or using #initialxy's (better!) suggestion:
if (provider in config.providers[0]) {
...
}
How about:
for (provider in config.providers[0].filter(function(a) {return a === "save"}) {
...
}
Strategy, you are looking for some kind of strategy pattern as,
Currenlty the save is hardcoded but what will you do if its coming from other varible – Al Bundy
var actions = {
save: function() {
alert('saved with args: ' + JSON.stringify(arguments))
},
delete: function() {
alert('deleted')
},
default: function() {
alert('action not supported')
}
}
var config = {
providers: [{
'save': function() {
return {
action: 'save',
args: 'some arguments'
}
},
notSupported: function() {}
}]
}
for (provider in config.providers[0]) {
(actions[provider] || actions['default'])(config.providers[0][provider]())
}
Push „Run code snippet” button will shows two pop-ups - be carefull
It is not clearly stated by the original poster whether the desired output
should be a single save - or an array containing all occurrences of
save.
This answer shows a solution to the latter case.
const providers = ['save', 'hello', 'world', 'save'];
const saves = [];
_.forEach(_.filter(providers, elem => { return elem==='save' }),
provider => { saves.push(provider); });
console.log(saves);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.19/lodash.js"></script>
I was just going through the source of transit.js and came across the following fucntion ::
$.cssHooks['transit:transform'] = {
// The getter returns a `Transform` object.
get: function(elem) {
return $(elem).data('transform') || new Transform();
},
// The setter accepts a `Transform` object or a string.
set: function(elem, v) {
var value = v;
if (!(value instanceof Transform)) {
value = new Transform(value);
}
// We've seen the 3D version of Scale() not work in Chrome when the
// element being scaled extends outside of the viewport. Thus, we're
// forcing Chrome to not use the 3d transforms as well. Not sure if
// translate is affectede, but not risking it. Detection code from
// http://davidwalsh.name/detecting-google-chrome-javascript
if (support.transform === 'WebkitTransform' && !isChrome) {
elem.style[support.transform] = value.toString(true);
} else {
elem.style[support.transform] = value.toString();
}
$(elem).data('transform', value);
}
};
I understand the latter part of the function, but its really hard to understand the initial part of the function, the function can be found on git too , HERE .
Initially I see this, $.cssHooks['transit:transform'] what is that line really saying?
After that we have the below line of code I.E. the getter and setter method,
set: function(elem, v) {
But who is passing the elem and v inside the function, I don't see anything being passed?
Read about cssHooks at jQuery cssHooks
Look at the source code (search for hooks.get and hooks.set)
.cssHooks is an array of objects that contains getter and setters tha will be executed by .css(). Thats all.
$.cssHooks['transit:transform'] = {set: function(elem,value){}, get: function(elem){}}
equal:
$.cssHooks['transit:transform'] = {};
$.cssHooks['transit:transform'].set = function(elem, value){};
$.cssHooks['transit:transform'].get = function(elem){};
$(element).css('transit:transform',value)
comes to:
$.cssHooks['transit:transform'].set(element,value)
$(element).css('transit:transform')
comes to:
$.cssHooks['transit:transform'].get(element)
$.cssHooks['transit:transform'] = {set:function(){}, get: function(){} }
{...} is an object creation.get and set not executed at this moment.
They created {set:function(){}, get: function(){} }
So. Simply: .css() will execute set and get functions for hooked property.
If you want to know how real getters and setters works:
Object.defineProperty()
In Javascript, you can add/access to a property with this syntax :
myObject.myProperty
or with this syntax :
myObject['myProperty']
This is the same result
So your line
$.cssHooks['transit:transform']
just mean that we want to store an object (code between {} in your original post) inside the 'transit:transform' property which is inside the cssHooks property which is inside the $ object
This is the same things :
$['cssHooks']['transit:transform']
The reason why they use the [''] syntax is that transit:transform contains the ':' char which is not allowed if you want to access it this way :
$.cssHooks.transit:transform //doesn't work
EDIT:
To answer to your second question, i don't know...the code you are showing is just the 'description' of the "transit:transform' property
I am working with AngularJS and have created a Module, which has a Factory and a Filter. The Factory gets a local json file translations) and the filter provides a function that returns a translated version of the text. So the code looks like the following;
angular
.module('i18n', [])
.factory('translationDataFact', ['$http', function($http){
var t = {};
var user = {};
t.defaultLanguage = 'en-GB';
t.languageFile = null;
t.init = function(){
t.setLanguage();
if(!t.languageFile){
$http
.get('translations/' + t.defaultLanguage + '.json')
.success(function(data){
t.languageFile = data.strings;
})
.error(function(error){
console.log(error);
});
}
}
t.setLanguage = function(){
/* change default language to User language here */
if(user.id){
t.defaultLanguage = user.language;
}
return t.defaultLanguage;
}
t.init();
return t.languageFile;
}])
.filter('t', ['translationDataFact', function (translationDataFact) {
var translate = function (stringIdenitfier) {
var translation = translationDataFact.languageFile[stringIdenitfier];
if(translation){
return translation;
}
return "translate me!!";
};
return translate(stringIdenitfier);
}]);
Then I wish to use the filter to translate variables and names like this
{{"string" | t }}
The problem I am having is that I have no idea how to make sure
The return of the Factory is set before the Filter runs this.
Also I am confused by how I prevent the whole application rendering until this filter is ready?
Any help would be amazing as I am lost :(
Is there a reason why you don't use an existing angularjs translation library like angular-translate?
In factory method u need to return Service itself, not result of operation. (I am not sure what exactly u want from this serivce)
when you return t.language it is always null and it will remain null in your filter... because your http call is asynchronious.
I would make this like:
app.module('translationDataFact', ['$resource', function($resource) {
var t = {};
t.init = function() {
t.result = $resource('...');
}
t.init()
return t;
}]);
In controller you have:
$scope.language = translationDataFact.result;
You make filter with parameter, inside filter you can check whether language is undefined or not.
So later you write:
{{ "string" | t:language}
And after language 'arrives' you see translation.
To answer your concerns:
Your factory should return something that can be asked for a specific translation. If the translations are not ready just return something basic like an empty string or null. e.g.
return translations.t(languageFile, translationKey);
Where t() would be a function that inspects the internal data structure of translations and can return either the result of the translation or the value mentioned earlier if the translations haven't been loaded yet.
You can do something like ng-show="translations.isLoaded()" on your top level element, but you'd need to set up a reference to the translations service on the $scope of your highest level controller. You may want to do this on the $rootScope so your translation service is always available in controllers as well.