inline conditionals in angular.js - angularjs

I was wondering if there is a way in angular to conditionally display content other than using ng-show etc. For example in backbone.js I could do something with inline content in a template like:
<% if (myVar === "two") { %> show this<% } %>
but in angular, I seem to be limited to showing and hiding things wrapped in html tags
<p ng-hide="true">I'm hidden</p>
<p ng-show="true">I'm shown</p>
What is the recommended way in angular to conditionally show and hide inline content in angular just using {{}} rather than wrapping the content in html tags?

Angular 1.1.5 added support for ternary operators:
{{myVar === "two" ? "it's true" : "it's false"}}

EDIT: 2Toad's answer below is what you're looking for! Upvote that thing
If you're using Angular <= 1.1.4 then this answer will do:
One more answer for this. I'm posting a separate answer, because it's more of an "exact" attempt at a solution than a list of possible solutions:
Here's a filter that will do an "immediate if" (aka iif):
app.filter('iif', function () {
return function(input, trueValue, falseValue) {
return input ? trueValue : falseValue;
};
});
and can be used like this:
{{foo == "bar" | iif : "it's true" : "no, it's not"}}

Thousands of ways to skin this cat. I realize you're asking about between {{}} speifically, but for others that come here, I think it's worth showing some of the other options.
function on your $scope (IMO, this is your best bet in most scenarios):
app.controller('MyCtrl', function($scope) {
$scope.foo = 1;
$scope.showSomething = function(input) {
return input == 1 ? 'Foo' : 'Bar';
};
});
<span>{{showSomething(foo)}}</span>
ng-show and ng-hide of course:
<span ng-show="foo == 1">Foo</span><span ng-hide="foo == 1">Bar</span>
ngSwitch
<div ng-switch on="foo">
<span ng-switch-when="1">Foo</span>
<span ng-switch-when="2">Bar</span>
<span ng-switch-default>What?</span>
</div>
A custom filter as Bertrand suggested. (this is your best choice if you have to do the same thing over and over)
app.filter('myFilter', function() {
return function(input) {
return input == 1 ? 'Foo' : 'Bar';
}
}
{{foo | myFilter}}
Or A custom directive:
app.directive('myDirective', function() {
return {
restrict: 'E',
replace: true,
link: function(scope, elem, attrs) {
scope.$watch(attrs.value, function(v) {
elem.text(v == 1 ? 'Foo': 'Bar');
});
}
};
});
<my-directive value="foo"></my-directive>
Personally, in most cases I'd go with a function on my scope, it keeps the markup pretty clean, and it's quick and easy to implement. Unless, that is, you're going to be doing the same exact thing over and over again, in which case I'd go with Bertrand's suggestion and create a filter or possibly a directive, depending on the circumstances.
As always, the most important thing is that your solution is easy to maintain, and is hopefully testable. And that is going to depend completely on your specific situation.

I am using the following to conditionally set the class attr when ng-class can't be used (for example when styling SVG):
ng-attr-class="{{someBoolean && 'class-when-true' || 'class-when-false' }}"
The same approach should work for other attribute types.
(I think you need to be on latest unstable Angular to use ng-attr-, I'm currently on 1.1.4)
I have published an article on working with AngularJS+SVG that talks about this and related issues. http://www.codeproject.com/Articles/709340/Implementing-a-Flowchart-with-SVG-and-AngularJS

For checking a variable content and have a default text, you can use:
<span>{{myVar || 'Text'}}</span>

If I understood you well I think you have two ways of doing it.
First you could try ngSwitch and the second possible way would be creating you own filter. Probably ngSwitch is the right aproach but if you want to hide or show inline content just using {{}} filter is the way to go.
Here is a fiddle with a simple filter as an example.
<div ng-app="exapleOfFilter">
<div ng-controller="Ctrl">
<input ng-model="greeting" type="greeting">
<br><br>
<h1>{{greeting|isHello}}</h1>
</div>
</div>
angular.module('exapleOfFilter', []).
filter('isHello', function() {
return function(input) {
// conditional you want to apply
if (input === 'hello') {
return input;
}
return '';
}
});
function Ctrl($scope) {
$scope.greeting = 'hello';
}

Angular UI library has built-in directive ui-if for condition in template/Views upto angular ui 1.1.4
Example:
Support in Angular UI upto ui 1.1.4
<div ui-if="array.length>0"></div>
ng-if available in all the angular version after 1.1.4
<div ng-if="array.length>0"></div>
if you have any data in array variable then only the div will appear

if you want to display "None" when value is "0", you can use as:
<span> {{ $scope.amount === "0" ? $scope.amount : "None" }} </span>
or true false in angular js
<span> {{ $scope.amount === "0" ? "False" : "True" }} </span>

So with Angular 1.5.1 ( had existing app dependency on some other MEAN stack dependencies is why I'm not currently using 1.6.4 )
This works for me like the OP saying {{myVar === "two" ? "it's true" : "it's false"}}
{{vm.StateName === "AA" ? "ALL" : vm.StateName}}

Works even in exotic Situations:
<br ng-show="myCondition == true" />

I'll throw mine in the mix:
https://gist.github.com/btm1/6802312
this evaluates the if statement once and adds no watch listener BUT you can add an additional attribute to the element that has the set-if called wait-for="somedata.prop" and it will wait for that data or property to be set before evaluating the if statement once. that additional attribute can be very handy if you're waiting for data from an XHR request.
angular.module('setIf',[]).directive('setIf',function () {
return {
transclude: 'element',
priority: 1000,
terminal: true,
restrict: 'A',
compile: function (element, attr, linker) {
return function (scope, iterStartElement, attr) {
if(attr.waitFor) {
var wait = scope.$watch(attr.waitFor,function(nv,ov){
if(nv) {
build();
wait();
}
});
} else {
build();
}
function build() {
iterStartElement[0].doNotMove = true;
var expression = attr.setIf;
var value = scope.$eval(expression);
if (value) {
linker(scope, function (clone) {
iterStartElement.after(clone);
clone.removeAttr('set-if');
clone.removeAttr('wait-for');
});
}
}
};
}
};
});

More recent angular, 6+ I think. You have ng-template with the else conditional hooking up to a tag identifier:
<div *ngIf="myVar else myVarNo">Yes</div>
<ng-template #myVarNo><div>No</div></ng-template>

Related

Angularjs Interpolation using double curly braces not working under ng-if

UPDATE1: developed the plunker sample that will reproduce the problem. See below.
I have a strange problem in my project, where it appears in one place only. Finally, I was able to reproduce the problem using plunker sample:
http://plnkr.co/edit/JJbq54?p=preview
In the above sample, see the section "With ng-if" and "Without ng-if", enter something in the input text, and see how the double curly braces not working under ng-if, but ng-bind works fine. Also, if you remove check-if-required from the template sites-and-improvements.html also the problem is solved.
More details below:
I have the the following HTML5 code block:
<div ng-if="isFullCUSPAP" id="sites_and_imrpovements_comments">
<div class="form-row">
<div class="inputs-group">
<label>WIND TURBINE:</label>
<div class="input-binary">
<label>
<input type="radio" id="wind_turbine"
name="wind_turbine"
ng-model="$parent.wind_turbine"
value="Yes" force-model-update />
Yes
</label>
</div>
<div class="input-binary">
<label>
<input type="radio" id="wind_turbine"
name="wind_turbine"
ng-model="$parent.wind_turbine"
value="No" force-model-update />
No
</label>
</div>
<span ng-bind="wind_turbine"></span>
<span>wind_turbine = {{wind_turbine}}</span>
</div>
</div>
</div>
I know that ng-if will create a new child scope. See above code, scope variable wind_trubine. Only in this HTML5 file, the curly braces {{}} is not working. However, if I use ng-bind it works fine. In other HTML5 files, I have no problem what so ever. This HTML5 is implemented using directive as follows:
app.directive('sitesAndImprovements', function() {
return {
restrict: 'E',
replace:true,
templateUrl: '<path-to-file>/site-and-improvments.html',
link: function (scope, elem, attrs) {
//Business Logic for Sites and Improvements
}
}
})
And, simply, I put it in the parent as follows:
<sites-and-improvements></sites-and-improvements>
The only difference I could see, is that this implementation has two levels of nested ng-if, which would look like the following:
<div ng-if="some_expression">
...
...
<sites-and-improvements></sites-and-improvements>
...
...
</div>
Based on comments, I used controller As notation and defined MainController accordingly. See snapshots below. It seems there is a problem if ng-if is nested with two levels. The scope variable is completely confused. I don't get the same results using ng-bind and double curly braces.
If you examine the above snapshots, even though I used controller As notation, you will see that ng-bind gives different results when compared with interpolation using {{}}.
I even changed the default value of wind_turbine to be set as follows in the link function:
scope.MainController.wind_turbine = 'Yes';
I noticed that on page load, everything looks fine, but when I change the value of the input element wind_trubine using the mouse, all related reference are updated correctly except the one that uses {{}}.
Maybe this is because there are two nested levels of ng-if?
Appreciate your feedback.
Tarek
Remove the replace: true from the sites-and-improvements directive:
app.directive('sitesAndImprovements', function() {
return {
restrict: 'E',
̶r̶e̶p̶l̶a̶c̶e̶:̶t̶r̶u̶e̶,̶
templateUrl: 'site-and-improvments.html',
link: function (scope, elem, attrs) {
//debugger;
}
}
})
It is fighting the check-if-required directive:
app.directive('checkIfRequired', ['$compile', '$timeout', function ($compile, $timeout) {
return {
priority: 2000,
terminal: true,
link: function (scope, el, attrs) {
el.removeAttr('check-if-required');
$timeout(function(){
//debugger;
$(':input', el).each(function(key, child) {
if (child && child.id === 'test_me') {
angular.element(child).attr('ng-required', 'true');
}
if (child && child.id === 'testInput1') {
//debugger;
//angular.element(child).attr('ng-required', 'true');
}
});
$compile(el, null, 2000)(scope);
})
}
};
}])
The DEMO on PLNKR.
replace:true is Deprecated
From the Docs:
replace ([DEPRECATED!], will be removed in next major release - i.e. v2.0)
specify what the template should replace. Defaults to false.
true - the template will replace the directive's element.
false - the template will replace the contents of the directive's element.
-- AngularJS Comprehensive Directive API - replace deprecated
From GitHub:
Caitp-- It's deprecated because there are known, very silly problems with replace: true, a number of which can't really be fixed in a reasonable fashion. If you're careful and avoid these problems, then more power to you, but for the benefit of new users, it's easier to just tell them "this will give you a headache, don't do it".
-- AngularJS Issue #7636
For more information, see Explain replace=true in Angular Directives (Deprecated)
Another solution posted by AngularJS team here:
https://github.com/angular/angular.js/issues/16140#issuecomment-319332063
Basically, they recommend to convert the link() function to use compile() function instead. Here is the update code:
app.directive('checkIfRequired', ['$compile', '$timeout', function ($compile, $timeout) {
return {
priority: 2000,
terminal: true,
compile: function (el, attrs) {
el.removeAttr('check-if-required');
var children = $(':input', el);
children.each(function(key, child) {
if (child && child.id === 'test_me') {
angular.element(child).attr('ng-required', 'true');
}
});
var compiled = $compile(el, null, 2000);
return function( scope ) {
compiled( scope );
};
}
};
}]).directive('sitesAndImprovements', function() {
return {
restrict: 'E',
replace:true,
templateUrl: 'site-and-improvments.html'
}
});
The main problem I have with this solution is that I am using the scope parameter which is passed to the link() function. For example, in the .each() loop above, I need to get the value of the element ID which is based on interpolation using {{<angular expre>}}.
So I tried to use pre-link and post-link within the compile function where the scope is available. I noticed that the section with ng-if is removed when execution is in pre-link and then it is added shortly after that. So I had to use $watch to monitor changes to the children to run the needed process when required. I developed this plunker sample:
http://plnkr.co/edit/lsJvhr?p=preview
Even after all such effort, the issue is not resolved. So the bottom line for similar cases, is that if you need to use the scope then you have to remove replace: true.
Any feedback would be appreciated.
Tarek

Is there an easy way to extend an attribute directive in angularjs?

I want to make a custom ng-if but can't find any good examples of how that should be done.
What I'm aiming for is:
<div my-if="someText">....</div>
I want that to expand to
<div ng-if="true|false">....</div>
where true|false depends on someText. Is there an easy way to do this?
To replace a directive with an ng-if a recompile is needed. The easiest way I get it working was this:
(function () {
angular.module('enheter').directive('toggleIf',['$compile', function($compile) {
return {
restrict: 'A',
compile: function(el,attr) {
var toggleName = attr.toggleIf;
var toggleOn = toggleName === "sometext";
el.attr('ng-if', toggleOn);
el.removeAttr('toggle-if');
var fn = $compile(el);
return function(scope) {
fn(scope);
};
}
};
}]);
})();
The directive above first get the value of the toggleIf attribute. The part that define the value of toggleOn will be more complex, but this shows what I was aiming for. Then I just add the ng-if to the element and remove the toggle-if. If the toggle-if was not removed there would be an infinite loop since the call to $compile would execute this function again and again.
<div ng-if="showText ? true : false">....</div>
but then again, if your showText is a boolean, it will do the same thing.

Move function from controller to directive

I have a function in my controller that manipulates the DOM. I understand this to be a bad practice and DOM manipulations should be moved into a directive. I'm having trouble pulling it out of the controller and into its own directive.
I have the following example code in my controller:
$scope.sidebarToggle = function() {
if ($scope.request = null) {
$(#container).switchClass('bottom', 'top', 400, 'linear');
$scope.editing = true;
}
else {
$(#container).switchClass('top', 'bottom', 400 'linear');
$scope.editing = false;
{
};
The above code's if conditions are very simplified, in the live code there are multiple conditions. Otherwise an ng-show/hide directive might have been possible.
The purpose of the code is to recognize the state the user is in, reveal/hide an off-screen sidebar (the class assignments), and set the 'editing' state of the controller.
How can this be refactored into a directive to accomplish the same goal?
Take a look at the documentation for angular directives to get started.
https://docs.angularjs.org/guide/directive
For 'angularising' the class of the container, use ng-class.
example # Adding multiple class using ng-class
You probably don't need to create your own directive for that.
Angular have already created some directives that could help you out.
In your case, you should use angular directive : ng-show and ng-class or ng-style
Exemple :
HTML
<div ng-show="request == null"> Edit </div>
<div ng-class="{'class-top': request == null,'class-bottom' : request != null}"> <div>
CSS :
.class-top{
...
}
.class-bottom{
...
}
Let me know if it works for you,
Nico
Try this:
app.directive('test', function() {
return {
restrict: 'E',
scope: {
},
link: sidebarToggle
};
});
I think it's creating a directive and link to your function sidebarToggle

Angular "bind twice"

I'm trying to keep my watches down by using one-time binding (::) in most places.
However, I've run into the situation where I need to wait for one property of an object to arrive from our server.
Is there someway I can make Angular bind twice (first to a placeholder and second to the actual value)?
I tried accomplishing this using bindonce but it did not seem to work (I am guessing this is because bindonce wants to watch an entire object, not a single property).
Another solution would be if I could somehow remove a watch from the templates after the value comes in, if that is possible.
My objects look something like this:
{
name: 'Name',
id: 'Placeholder'
}
And my template:
<div ng-repeat="object in objects">
{{::object.name}}
{{::object.id}}
</div>
Id will change once and only once in the application life time, having a watch forever for a value that will only change once feels wasteful as we'll have many of these objects in the list.
I think this is what you are looking for! Plunkr
I just wrote a bind-twice directive and if I did not completely missed the question it should solve your problem:
directive("bindTwice", function($interpolate) {
return {
restrict: "A",
scope: false,
link: function(scope, iElement, iAttrs) {
var changeCount = 0;
var cancelFn = scope.$watch(iAttrs.bindTwice, function(value) {
iElement.text(value === undefined ? '' : value);
changeCount++;
if (changeCount === 3) {
cancelFn();
}
});
}
}
});
What I do is, I add a watcher on the scope element we need to watch and update the content just like ng-bind does. But when changeCount hit the limit I simply cancel $watch effectively cleaning it from watchlist.
Usage:
<body ng-controller="c1">
<div ng-repeat="t in test">
<p>{{ ::t.binding }}</p>
<p bind-twice="t.binding"></p>
<p>{{ t.binding }}</p>
</div>
</body>
Please see Plunkr for working example.

How to use a dynamic value with ngClass

I'm trying to apply a class name that's the same as a scope variable.
For example:
<div ng-class="{item.name : item.name}">
So that the value of item.name is added to the class. This doesn't seem to do anything though. Any suggestions on how to do this?
Thanks!
EDIT:
This is actually being done within a select, using ng-options. For example:
<select ng-options="c.code as c.name for c in countries"></select>
Now, I want to apply a class name that has the value of c.code
I found the following directive, which seems to work, but not with interpolation of the value:
angular.module('directives.app').directive('optionsClass', ['$parse', function ($parse) {
'use strict';
return {
require: 'select',
link: function(scope, elem, attrs, ngSelect) {
// get the source for the items array that populates the select.
var optionsSourceStr = attrs.ngOptions.split(' ').pop(),
// use $parse to get a function from the options-class attribute
// that you can use to evaluate later.
getOptionsClass = $parse(attrs.optionsClass);
scope.$watch(optionsSourceStr, function(items) {
// when the options source changes loop through its items.
angular.forEach(items, function(item, index) {
// evaluate against the item to get a mapping object for
// for your classes.
var classes = getOptionsClass(item),
// also get the option you're going to need. This can be found
// by looking for the option with the appropriate index in the
// value attribute.
option = elem.find('option[value=' + index + ']');
// now loop through the key/value pairs in the mapping object
// and apply the classes that evaluated to be truthy.
angular.forEach(classes, function(add, className) {
if(add) {
angular.element(option).addClass(className);
}
});
});
});
}
};
}]);
Better later than never.
<div ng-class="{'{{item.name}}' : item.condition}">
yes. ' and {{ for classname.
I'm on angular 1.5.5 and none of these solutions worked for me.
It is possible to use the array and map syntax at once though it's only shown in the last example here
<div ng-class="[item.name, {'other-name' : item.condition}]">
Simply using the variable should be sufficient:
<div ng-class="item.name" />
This is also documented in the official documentation.
I think you missed the concept.
A conditional css class looks like this:
<div ng-class="{'<css_class_name>': <bool_condition>}">
And I dont think you want:
<div ng-class="{'true': true}">
You probally want to use:
<div ng-class="item.name"></div>
Angularjs Apply class with condition:
<div ng-class="{true:'class1',false:'class2'}[condition]" >
This can be useful in some cases:
HTML:
<div ng-class="getCssClass()"></div>
JS:
$scope.getCssClass = function () {
return { item.name: item.name };
};

Resources