ng-class one time binding - angularjs

I'm wondering if it's possible to have a ng-class with class one time binded and class which are evaluated each digest cycle.
<div ng-class="{'one_time_binded_class': isMonkey(), 'not_one_time_binded_class': isUnicorn()}"></div>
I know I can one time bind the complete ng-class with ng-class="::{...}"
but my need is to one time bind a particular expression
Of course, this thing doesn't work :
<div ng-class="{'my_static_class': ::isMonkey(), 'my_dynamic_class': isUnicorn()}"></div>
Is there a way to do it ?

Method 1:
class="some-class {{::expression ? 'my-class' : ''}}"
Method 2:
ng-class="::{'my-class': expression}"

One way I can think of doing this (if I followed what you were trying to say) is as follows...
.blue{
color: blue;
}
.underline{
text-decoration: underline;
}
.lineThrough{
text-decoration: line-through;
}
<div ng-app ng-controller="myCtrl">
<p ng-class="{'blue': isMonkey()}" class="{{isUnicorn() ? dynamicClass: ''}}">My Text</p>
<button ng-click="monkey = !monkey">Monkey</button>
<button ng-click="unicorn = !unicorn">Unicorn</button>
<button ng-click="toggleClass()">Toggle</button>
</div>
function myCtrl($scope) {
$scope.dynamicClass = "underline";
$scope.monkey = true;
$scope.unicorn = true;
$scope.isMonkey = function () {
return $scope.monkey;
}
$scope.isUnicorn = function () {
return $scope.unicorn;
}
$scope.toggleClass = function(){
$scope.dynamicClass = $scope.dynamicClass === "underline"? "lineThrough": "underline";
}
}
JSFiddle

An important part of one time binding is that it not be bound until the 'expression' is not undefined. The best answer so far, by #ifadey, his Method 1 evaluates to an empty string when 'expression' is undefined, which get's bound. This is contrary to the expected feature behavior. Method 2 is equally unhelpful in this late binding scenario.
Do do this correctly, directly answering op's question:
class="some-class {{::expression ? 'one-time-class' : undefined}}"
ng-class="{ 'my-dynamic-class' : expression2 }"
or the more technically correct but ugly:
class="some-class {{::expression ? 'one-time-class' : (expression===undefined ? undefined : '')}}"

Related

AngularJS ng-class the same class with different conditions

I have a option in the back-end to let user change icon in default and in hover state. This is the code
ng-class="{'{{getAnswerButtonHoverIcon()}}':hovering, '{{getAnswerButtonIcon()}}' : !hovering}
But the problem is, when the user selected the same icon for default state and hover state, like for example they have selected "fa-circle-o" the output of the HTML will be
ng-class="{'fa-circle-o':hovering, 'fa-circle-o' : !hovering}"
and it doesn't work anymore. When I try to test it out, it removes the "fa-cricle-o" class on hover. Any idea why is it happening? And if you can suggest better solution, it will be greatly appreciated. Thanks!
I mentioned in a comment that I would probably have a single function but I think I misunderstood your question at first. Building on Joshua's approach here is something that could work for you: if the two functions return the same value then it still works.
I feel that this is not the nicest solution since it is generating a string into a class, but the same can be changed to ng-class and {hovering ? sg() : sgelse()} as value. I hope it shows you a way to handle these kind of interactions.
Let me know if it does/doesn't work so that we could further help you.
var app = angular.module("mainModule",[]);
app.controller("mainCtrl", function($scope){
$scope.icon = {};
$scope.icon.hovering = false;
$scope.getAnswerButtonHoverIcon = function(){
return 'bluebg';
};
$scope.getAnswerButtonIcon = function(){
return 'redbg';
};
});
.bluebg{background-color:blue}
.redbg{background-color:red}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<div ng-app="mainModule">
<div ng-controller="mainCtrl">
<div class="{{ icon.hovering ? getAnswerButtonHoverIcon() : getAnswerButtonIcon() }}"
ng-mouseenter="icon.hovering = true" ng-mouseleave="icon.hovering = false">
<p>I have different backgrounds</p>
</div>
</div>
</div>
You can also use the ng-class like this, which will just apply the class the function outputs:
ng-class="(hovering ? '{{getAnswerButtonHoverIcon()}}': '{{getAnswerButtonIcon()}}')

ng-class not working with ng-repeat

I have a situation where I have to add class according to the condition and the ng-class is working according to it even the condition in the ng-class is true.
<ul id="" class="clowd_wall" dnd-list="vm.cardData[columns.id].data"
dnd-drop="vm.callback(item,{targetList: vm.cardData[columns.id].data, targetIndex: index, event: event,item:item,type:'folder',eventType:'sort','root':'folder',current_parent:'folder'})" ng-model="vm.cardData[columns.id].data">
<div class="emptyCol" ng-if="vm.cardData[columns.id].data.length==0">Empty</div>
<li class="dndPlaceholder"></li>
<li class="cont____item" ng-repeat="card in vm.cardData[columns.id].data | orderBy:vm.sort" dnd-draggable="card"
dnd-effect-allowed="move"
dnd-allowed-types="card.allowType"
dnd-moved="vm.cardData[columns.id].data.splice($index, 1)"
dnd-selected="vm.tree.selected = card" ng-class="{emptyCard:card.data.length==0,zoomin:vm.zoomin=='zoomin',emptyCard:!card.data}">
<div class="item" style="height:79%">
<ng-include ng-init = "root = columns.id" src="'app/partials/card.html'"></ng-include>
</div>
</li>
</ul>
ng-class="{'emptyCard': (!card.data || !vm.cardData[columns.id].data.length),'zoomin':(vm.zoomin=='zoomin')}">
Seems like you want to use vm.cardData[columns.id].data.length instead of card.data.length
Your question is not clear as don't know what card.data will contain and ".data" will be present for each iteration
If it is array then this will work "card.data.length" and if there is no "data" key in "card" then ".length" will through error i.e. if card.data itself undefined then it will not have "length" property.
Try to add condition in ng-class one by one then you will be able to figure out which condition is causing problem.
Made some small change
ng-class="{emptyCard: card.data.length==0 || !card.data,zoomin: vm.zoomin=='zoomin'}"
If you have multiple expression, try old fashioned, if it looks best:
Controller:
$scope.getcardClass = function (objCard, strZoomin) {
if (!card.data) {
return 'emptyCard';
} else if (strZoomin =='zoomin') {
return 'zoomin';
} else if (card.data.length == 0) {
return 'emptyCard';
}
};
HTML:
ng-class="vm.getcardClass(card, vm.zoomin)"
NOTE: Replace vm with your controller object.

ngClass Directive: Can I use multiple expressions to add multiple classes?

Here is an example of what I want to achieve
data-ng-class="{ 'tooltip_show' : showTooltip , 'tooltip__' + brand.settings.name }"
but it doesn't work.
Use the array form for ng-class:
<div ng-class="[showTooltip ? 'tooltip_show' : '',
'tooltip__' + brand.settings.name]">
<div>
OR compute the class in JavaScript:
<div ng-class="computeClass(tooltip_show, brand.setting.name)">
</div>
$scope.computeClass(show, name) {
var obj = {};
obj.showTooltip = show;
obj['tooltip_'+name] = true;
return obj;
};
The later approach is more easily debugged and better for complex computation.
See also,
AngularJS ng-class Directive Reference - Known Issues
AngularJS Developer Guide - Why mixing interpolation and expressions is bad practice
It looks like you haven't set a value for the second item. Did you mean something like
{ 'tooltip_show' : showTooltip , 'tooltip__' + brand.settings.name : tooltipText }
or
{ 'tooltip_show' : showTooltip , 'tooltip__' : brand.settings.name }
?
http://jsbin.com/genaqapefe/edit?html,js,output
data-ng-class="{ 'tooltip_show': showToolTip, {{ 'tooltip_' + brand.settings.name }}: true }"
This is working for me in this bin. I couldn't get it to evaluate without the curly braces, although not sure if that's the best practice.

ng-class with multiple options

I'm fetching data using a REST API and one of the attributes returned can be any of 3 options;
The options are; Negative, Warning, Success.
I want to set a class using ng-class based on the value returned;
I'm able to do this but only for one;
Code below;
<div class="alert" ng-class="restaurantNotice(restaurant[0].notice)" ng-if="restaurant[0].notice.length">
<div class="alert-inner inner-large" ng-bind="restaurant[0].notice"></div>
</div>
$scope.restaurantNotice = function(a) {
return (a = Negative) ? 'negative' : '';
};
As you can see, if a = Negative then a class negative is applied else nothing is applied.
Now I want to check for all three options and apply 3 different classes respectively
Instead of function in ng-class, use object
ng-class="{'negative' : restaurant[0].notice == 'Negative', 'warning' : restaurant[0].notice == 'Warning', 'success' : restaurant[0].notice == 'Success'}"
You can try
<div class="alert" ng-class="{'negative' : restaurant[0].notice.length, 'warning' : restaurant[1].notice.length, 'success' : restaurant[2].notice.length}">
<div class="alert-inner inner-large" ng-bind="restaurant[0].notice"></div>
</div>
duplicate of Adding multiple class using ng-class
In this way:
<p ng-class="{a: deleted, b: important, c: error}">Example</p>
if a is true, apply class deleted, and so on.
If you are just going to set the same notice type (Negative, Warning, Success) as class then just convert to lower case and return from the function instead of putting conditions.
$scope.restaurantNotice = function(a) {
return a.toLowerCase();
};
OR
<div class="alert" class="{{restaurant[0].notice | lowercase}}" ng-if="restaurant[0].notice.length">

ngClass string binding expression with class map

Is it possible to use ngClass with an expression AND a class map? I want to conditionally add a class based on the existence of a variable as well as use that variable in the expression that creates the class.
For instance, if isActive() is true and getStatus() returns "valid" I want the class list to be "element element--active element--valid". If getStatus() returns undefined I want the class list to be "element element--active".
<div
class="element"
ng-class="{
'element--active': ctrl.isActive(),
'element--{{ ctrl.getStatus() }}': ctrl.getStatus()
}"></div>
Doesn't seem to work.
<div
class="element element--{{ctrl.getStatus()}}"
ng-class="{
'element--active': ctrl.isActive()
}"></div>
Works but then there's an extra hanging "element--" if getStatus() returns undefined.
Do I have to add a method in my controller to handle the class generation?
i'd suggest to make just one function call to get the classes. It will make it cleaner and have the class logic in one place.
In your controller:
this.getElementStatus = function(){
var rules = {active:this.isActive()}; //Prefix with element-- {element--active:}
rules[this.getStatus()] = true; //Prefix with element--, rules['element--' + this.getStatus()] = true
return rules;
}
and your view would just be:
<div
class="element"
ng-class="ctrl.getElementStatus()"></div>
It seems like your element-- is redundant with the rule instead make use of cascadeability(CSS) property. and define rules as :
Example:
.element.active{ /*....*/ }
.element.success {/*...*/}
.element.error{/*...*/}
This will help in maintenance, gets more verbose and get to the natural way of adding css rules and could remove these kind of complexities from the view.
You could as well do:
<div class="element"
ng-class="{'active': ctrl.isActive(), '{{ctrl.getStatus()}}':true}"
or :
<div class="element"
ng-class="[ctrl.isActive() ? 'active' : '', ctrl.getStatus()]"
If you don't mind getting a true added as a rule(should not affect anything anyways) then,
<div class="element"
ng-class="[!ctrl.isActive() || 'element--active' , 'element--' + ctrl.getStatus()]">
You can use class and ng-class map on the same element. But since your class name is dynamic you will have to something like this.
<div
ng-class="'element '
+ (ctrl.isActive() ? ' element--active' : '')
+ (ctrl.getStatus() ? ' element--' + ctrl.getStatus() : '')"></div>

Resources