I am rather new to AngularJs, but I have a specific need for a more complex, conditional template using multiple interpolation symbols. I am using the same example as in https://docs.angularjs.org/api/ng/service/$interpolate .
I need something like:
[[ {{greeting}}, {{name}} || Hello, {{name}} || Hello, stranger ]]
This should be interpreted as a multiple conditional template, showing the first fragment if both $scope.greeting and $scope.name are defined, the second one if only $scope.name is defined, and the third one otherwise.
The idea is that within symbols [[ ]] the fragments between a || symbol are interpolated using the standard interpolation symbols with AllOrNothing, proceeding from left to right until the first one succeeds, and making sure that the last one always succeeds.
I know that this can be done with something like
<span ng-if='greeting && name">{{greeting}}{{name}}</span>
<span ng-if='name && !greeting">Hello, {{name}}</span>
<span ng-if='!name">Hello, stranger</span>
but this solution is extremely cumbersome, requires to determine which complex set of boolean expressions makes sure that only one span is shown, and adds spurious spans to the DOM just because you need a place for the ng-if directives.
Thank you for all you can suggest.
You can write your own filter to handle this situation specifically. If you want something a little more reusable, in regards to conditional output, you could make an a kind of ternary filter. Here's one called iif (named as such to prevent eval errors we'd get if we called it just if):
.filter('iif', function() {
// usage: {{ conditionToTest | iif:truevalue:falseValue }}
// example: {{ iAmTrue | iif:'I am true':'I am false' }}
return function(input, trueValue, falseValue) {
return input ? trueValue : falseValue;
};
})
Use it like this in your example:
{{greeting | iif:greeting:'Hello'}}, {{name | iif:name:'stranger'}}
You can certainly specialize it further, if that's too verbose:
.filter('valueOrDefault', function() {
return function(input, defaultValue) {
return input || defaultValue;
};
})
Then your template looks like:
{{ greeting | valueOrDefault:'Hello' }}, {{name | valueOrDefault: 'stranger'}}
And so on.
The interpolator should be able to handle it.
<p>{{ greeting || 'Hello' }}, {{ name || 'Stranger' }}.</p>
Related
I have an image that I display using this:
<img data-ng-src="data:image/jpg;base64,{{selectedReport.reportImage.imageFile.data}}"/>
The above data is fetched from my database.
When the user clicks edit record and selects a new image which is stored in variable imageFile, I want to show this imageFile instead of the record fetched.
How do I use an if condition with data-ng-src?
Individually these work, but i want to apply an if condition where I say, if ImageFile, then
data-ng-src="{{imageFile}}"
else
data-ng-src="data:image/jpg;base64,{{selectedReport.reportImage.imageFile.data}}"
I tried to do like this:
data-ng-src = {{imageFile}} and data-err-src = "data:image/jpg;base64,{{selectedReport.reportImage.imageFile.data}}"/>
But this doesn't work.
I think you should just use a function, declared in your controller to deal with the situation.
In your controller :
$scope.getImage = function (){
return $scope.imageFile || [your_default_image_file];
}
In your HTML, something like :
<img data-ng-src="{{getImage()}}"/>
I hope it helps.
AngularJS views support binary operators
condition && true || false
So your img tag would look like this
<img data-ng-src="{{ imageFile != '' && imageFile || 'your-default-image' }}"/>
Note : You could use any condition to know if imageFile exists or has a value.
Note 2 : the quotes (ie 'your-default-image') are important here. It won't work without quotes.
I have a section of code that displays two different ways based on a condition. In both ways, there is a value that I want to check:
user.name
This is displayed on the page like
<span ng-show="showusername && something > 3">{{user.name}} (other stuff here)</span>
<span ng-show="showusername && something <= 3">{{user.name}}</span>
My problem is, this is used elsewhere on the page as well, and the protractor piece can't seem to find the binding if I use by.binding('user.name'), it finds multiple, and displays
Expected '' to equal 'Joe Smith'
You can filter out the visible elements only:
var visibleUserNames = element.all(by.binding("user.name")).filter(function (elm) {
return elm.isDisplayed().then(function (isDisplayed) {
return isDisplayed;
});
});
expect(visibleUserNames.count()).toEqual(1);
expect(visibleUserNames.first().getText()).toEqual("Joe Smith");
I'm new to angular and keep getting the following error in the console TypeError: name.replace is not a function. I'm not sure what's exactly causing it, but it seems to be caused by the ng-style statement and maybe something to do with the camelCase?
The part I don't understand is why ng-style="isFrontView() || !matches && {'display': 'none'}" throws the error, but ng-style="!isFrontView() || !matches && {'display': 'none'}" doesn't throw the error.
In an attempt to remedy the situation I tried removing the camelCase from the function name and went all lowercase. I also attempted to use !!isFrontView(), but neither seemed to remove the error message.
Do anyone know what is the cause of this error message and a potential fix?
HTML Template:
<div class="system-view">
<div class="controller-container fill" id="systemView1" ng-style="isFrontView() || !matches && {'display': 'none'}">
<canvas id="canvasLayer-shell" data-layername="front" width="617" height="427"></canvas>
<i ng-if="!matches" class="fa fa-repeat toggle-view" ng-click="changeView()" ng-touch="changeView()"></i>
</div>
<div class="controller-container fill" id="systemView2" ng-style="!isFrontView() || !matches && {'display': 'none'}">
<canvas id="canvasLayer-shell" data-layername="back" width="617" height="427"></canvas>
<i ng-if="!matches" class="fa fa-undo toggle-view" ng-click="changeView()" ng-touch="changeView()"></i>
</div>
</div>
Backend Code:
$scope.frontView = true;
$scope.matches = true;
$scope.isFrontView = function() {
return $scope.frontView;
};
$scope.changeView = function() {
$scope.frontView = !$scope.frontView;
};
P.S. Even with the console error everything still functions normally.
Your potential issue is due to the incorrect usage of ng-style. ng-style sets a watcher on the expression and sets the element's style with the help of jquery/jqlite element.css. And Inside element.css css attribute (name) is converted to the standard camel casing (which uses regex string replace). In your specific case the expression evaluated to boolean (true) instead of an object (ng-style does this for each property) and boolean does not have replace property (which is available on a string object) and hence it fails. You can test this by converting your expression to evaluate to a string by using string concatenation.
i.e ng-style="'' + (isFrontView() || !matches && {'display': 'none'})"
Looking at the expression all you need it to hide and show the element, you could well make use of ng-show/ng-hide directives to achieve that.
This can happen if the expression evaluated returns the wrong type.
Expression evaluated:
ng-style="$vm.getStyles()"
must return an object literal:
return { order: -1 };
This is a late answer but I might help others that have the same issue like me.In my case the error is a.replace is not a function and finally I found the reason. It was happening due to ng-style and the expression was data-ng-style="isCompare==true ? {'max-height':'423'} : ***' '*** .... that space between single qoutes caused the error.After removing space the error went away.
I'm using angular-translate with messageformat interpolation to pluralize some strings.
(for those who don't know what I'm talking about: http://angular-translate.github.io/docs/#/guide/14_pluralization).
It's going pretty well, but I can't figure out how to use variables instead of constants.
$translateProvider.translations('it', {
SELECTED_CATEGORIES: "{NUM, plural, =0{Nessuna categoria selezionata} one{1 categoria selezionata} other{# categorie selezionate}}"
}).translations('en', {
SELECTED_CATEGORIES: "{NUM, plural, =0{No category selected} one{1 selected category} other{# selected categories}}"
});
and this is the HTML code:
<span>{{ 'SELECTED_CATEGORIES' | translate:"{'NUM': 2 }" }}</span>
This works but if I use
<span>{{ 'SELECTED_CATEGORIES' | translate:"{'NUM': my_variable_in_the_scope }" }}</span>
I get an error. I tried to use quotes, double quotes and similar, but nothing seems to work.
I know that messageformat doesn't support expression evaluation, but I hoped that a variable substitution would have worked.
Any Idea?
Well, the correct solution should be passing the scope and accessing the value within the messageFormat code.
You can do this easily like this:
$translateProvider.translations('it', {
SELECTED_CATEGORIES: "{my_variable_in_the_scope , plural, =0{Nessuna categoria selezionata} one{1 categoria selezionata} other{# categorie selezionate}}"
}).translations('en', {
SELECTED_CATEGORIES: "{my_variable_in_the_scope , plural, =0{No category selected} one{1 selected category} other{# selected categories}}"
});
And your HTML:
<span>{{ 'SELECTED_CATEGORIES' | translate:your_scope }}</span>
Please note: I passed "your_scope" within the translate-filter and accessed "my_variable_in_the_scope" within the messageFormat code.
This should be the best solution.
To use variables in angular filters, you have to use
filter:{key: value} without quotes
E.g. my filter replaceVariable is used to enable rails yml placeholders being replaced with a js variable
usage:
{{ sometranslation | replaceVariable:{count:results} }}
filter:
// replaces {%count} in yml translations to work with angular
filters.filter('replaceVariable', function () {
"use strict";
return function (string, variable) {
var replace = string.replace(/%\{[\w\s]*\}/, variable.count);
return replace;
};
});
so i guess with translate you have to use it the same way. I remember i couldnt get this to work either which is why i chain my custom filter after
somevalue | translate | myCustomFilter
Is there a good way to dump or inspect the results of an expression? Sometimes when I do
{{some_expression}}
, nothing shows up on the page where the result of that expression should show up. How do I determine if the expression returned a null, an undefined, or an empty string ''?
If it's a normal object, like this, it will show a nice programmer-friendly representation of the object, which is great:
{{ {'a': 1} }}
But if you try to inspect an expression that evaluates to null, undefined, or '', they are all indistinguishable from each other!
{{null}}
{{undefined}}
{{''}}
So how can you tell which one it was??
I tried using JSON.stringify:
{{ JSON.stringify(null) }}
but JSON seems to be unavailable from an Angular expression because it's a method from window and not a property of the scope (see related question about accessing methods from window).
I tried using typeof:
typeof {}: {{ typeof {'a': 1} }}
but it results in an error:
Error: [$parse:syntax] Syntax Error: Token '{' is an unexpected token at column 9 of the expression [ typeof {'a': 1} ] starting at [{'a': 1} ].
So how can I get it to dump the value into the template using something like JSON.stringify (… or console.log)?
Generally speaking, is there a good way to debug Angular expressions other than trial and error? Since Angular expressions are so "forgiving", they don't seem to raise errors; they just silently swallow the errors and return undefined:
In JavaScript, trying to evaluate undefined properties generates
ReferenceError or TypeError. In Angular, expression evaluation is
forgiving to undefined and null.
But without seeing some kind of error message, how do you know what part of the expression Angular had trouble with?
You can add a custom filter for debugging purposes:
app.filter('debug', function() {
return function(input) {
if (input === '') return 'empty string';
return input ? input : ('' + input);
};
});
Usage:
{{ value | debug }}
Demo: http://plnkr.co/edit/U44BCjBhgsFedRkHASlc?p=preview
The recommended way is to use AngularJS's logging service $log. First you need to inject the service into your controller and assign it to a scope variable like so:
app.controller('MyCntrl', function($log){
$scope.$log = $log;
Then in your template, us it like ay other function:
<span>{{$log.log(value}}</span>
If you want to be able to use typeof, it works basically the same way:
$scope.getTypeOf = function(val){ return typeof val; };
<span>{{getTypeOf(val)}}</span>
The built-in JsonPipe might be easier...
{{ object | json }}
See: https://angular.io/api/common/JsonPipe
Based on tasseKATT's and ogc-nick's great answers, I added these two filters. They're written in CoffeeScript but here's the JavaScript equivalent if you prefer. Posting here in case it's helpful to anyone else...
.filter 'debug', ->
(input) ->
if typeof input is 'undefined'
'undefined'
else
JSON.stringify(input)
.filter 'typeof', ->
(input) ->
typeof input
Now I can get some meaningful debugging output from each of the following expressions:
{{null | debug}}
{{undefined | debug}}
{{'' | debug}}
If you're using Chrome, you can try the Batarang extension which allows you to inspect properties on your models. Access it by opening the developer console (CTRL+SHIFT+I), and then you should see a Batarang tab. You'll have to click to enable, and then you should get a listing of all Angular models present on your page. They won't be intuitively named, but you can click through them and deduce which ones are which.
Have you tried putting the console.log() inside the double curlies? That might also work if you just want a quick and lazy method.