angularjs how to trigger changes on object in scope - angularjs

I have the $scope object (array of objects) like this
$scope.parts = [];
(content of $scope.parts is changing during 'run-time', not just filled once per page load)
Later, it some custom directive i show those parts in such manner:
<li ng-repeat="part in parts">
<span>{{part.name}}
<i class="fa fa-check"
tooltip="some tooltip"
...
</i>
</span>
</li>
According to some logic, i want to change 'fa-' class and tooltip text.
I can do it like this
<i class="fa"
ng-class="haveDescr(part.name)"
//and in directive's controller
$scope.haveDescr = function (partName) {
return someCondition ? 'fa-check' : 'fa-question-circle';
};
and so on for the tooltip, and... for every attribute i want to change?
Is there a better way, than to write a scope "check-function" for every attribute? How can i trigger changes in every single part/property of $scope.parts and do the DOM changes described above? What is the right "angular way" for this? Or, maybe it is possible to 'intercept' ng-repeat action and do everything there?

You can use ng-class with an 'object' expression.
<i class="fa" ng-class="{'fa-check' : part.name, 'fa-question-circle' : !part.name}">

You can use ng-class and title
<i ng-class="{'fa-check':showFaCheck(part.name), 'fa-question': !showFaCheck(part.name) }" title="{{getTooltip(part.name)}}"/>
Fiddle http://jsfiddle.net/4PYZa/303/

Related

Why ng-show and ng-hide don't work with $route in angular?

In my application, I want to display a content based on active link, Example: if the link is "new" then "abc" and if link is "view" then "xyz" should be displayed. For that, I've used following code for my application.
<li>New</li>
<li>View</li>
<span class="white-text" ng-show="{{ $route.current.activetab === 'new' }}">new</span>
<span class="white-text" ng-show="{{ $route.current.activetab === 'view' }}">view</span>
When you use {{}}, the values are interpolated, i.e. the markup is replaced with the result of the expression. ngShow expects only the expression, so just use the function as it is, and it will work:
<span class="white-text" ng-show="$route.current.activetab === 'new' ">new</span>
<span class="white-text" ng-show="$route.current.activetab === 'view' ">view</span>
In general, you'll only want {{ }} when your expression / content should be displayed.
Hope this works!

Can I not use $ctrl. in angular component template

I am using angular 1.5 and I wanted to extract part of my DOM into a component.
Here is what I have done so far:
angular.module('my-app').component("menuItem",{
templateUrl : "lib/menu-item.tmpl.html",
bindings : {
index : "<",
first : "<",
last : "<",
item : "=",
onDelete : "&",
onMoveUp : "&",
onMoveDown : "&"
},
controller : function($scope) {
}
});
And the template looks like so:
<div>
<aside class="sort-buttons">
<ul>
<li>
<button ng-click="$ctrl.onMoveUp({index : $ctrl.index})"
ng-disabled="$ctrl.first">
<i class="icon icon-up"></i>
</button>
</li>
<li>
<button ng-click="$ctrl.onMoveDown({index : $ctrl.index})"
ng-disabled="$ctrl.last">
<i class="icon icon-down"></i>
</button>
</li>
</ul>
</aside>
<div class="row">
<button class="btn btn-danger btn-icon btn-remove"
ng-click="$ctrl.onDelete({index : $ctrl.index})">
<i class="icon icon-remove"></i>
</button>
</div>
</div>
I use this component (far from finished!) like so:
<section class="container menu">
<menu-item index="$index" first="$first" last="$last" item="item"
on-delete="removeItem(index)"
on-move-up="moveItemUp(index)"
on-move-down="moveItemDown(index)"
ng-repeat="item in menu">
</menu-item>
<!-- some other display details of `$ctrl.item` -->
</section>
I have three main questions I guess:
Why do I have to use $ctrl everywhere in my template? There is $scope so why all the bindings go to $ctrl rather than $scope? And is there a way to change this?
Can I somehow have values like $index, $first and $last passed in? It seems to me like it is a "buttery butter" to pass them in...
Is this even the right approach? Or should I use directive? I know components have isolated scope, and directives can have not-isolated scope. but could I mix/match in a directive (share the scope with controller, but also add my own functions to be used within directive/template only?)
Thanks for your help.
Why do I have to use $ctrl everywhere in my template? There is $scope
so why all the bindings go to $ctrl rather than $scope? And is there a
way to change this?
$scope will disappear with angular 2.0. You are not obliged to use $ctrl. I recommend that you still use "controllerAs" with a named controller, in order to avoid confusion inside your templates.
controllerAs: "menuItemCtrl",
controller : function($scope) {
},
and then :
<button ng-click="menuItemCtrl.onMoveUp({index : menuItemCtrl.index})"
ng-disabled="menuItemCtrl.first">
<i class="icon icon-up"></i>
</button>
to use your bounded variables inside your controller, you have to use this :
controller : function() {
var self = this;
// self.index contains your index
}
Can I somehow have values like $index, $first and $last passed in? It
seems to me like it is a "buttery butter" to pass them in...
I don't really understand how you want them to be passed.
Is this even the right approach? Or should I use directive?
When you're facing an application that can be displayed as a tree of components, components are the best option.

How to create dynamic expression in ng-class

I have a ngRepeat element that has a delete button that triggers a confirmation message. I'm trying to make the confirmation message show with a dynamic expression like so:
<div ng-repeat="item in items">
<a ng-click="delete(item_id)"></a>
<span ng-class="{'show':'delete_'+item._id}">Are you sure you want to delete your review? </span>
</div>
Unfortunately I can't get the expression 'delete_'+item._id to show up in the ngClass directive. Can someone please offer a solution?
Using ng-class="{'show': 'delete_' + item._id}" will add the class show whenever the expression 'delete_' + item._id' evaluates to true (which is always). You probably want to have an expression next to show which is true when the user has clicked the a tag, i.e. ng-class="{'show': userWantsToDelete(item)}".
Additionally, if you want to add the class 'delete_' + item._id to the element, you can use angular expressions with class attribute, i.e., class="{{'delete_' + item._id}}" on the element.
try this one.
<a ng-click="delete = !delete">delete</a>
<span ng-class="{show: delete}">Are you sure you want to delete your review? </span>
if you want to show one delete confirmation message of last clicked <a>
In view
<a ng-click="delete(item._id)">delete</a>
<span ng-class="{show: item._id==delete_id}">Are you sure you want to delete your review? </span>
In controller
$scope.delete = function(id) {
$scope.delete_id = ($scope.delete_id == id) ? !id : id;
}

Angular JS ng-repeat and the 'this'

i'm new in AngularJS, but did some jQuery before. i've got a problem to understand how to get the clicked element / it's parent to make some changes like change the text, an icon or a class in the item where i made the click.
the simple HTML:
<ul ng-controller="basketCtrl">
<li ng-repeat="item in item">
<button ng-click="addToBasket(Itemid,this,whatever)">
<i class="myBasketicon">
<span>Buy now</span>
</button>
</li>
</ul>
what i want to do:
$scope.addTobasket = function (id, elem, whatever){
// to some JSON-Server-stuff - that works perfect
// now my problems, :
//change this -> myBasketIcon -> myOKicon
//change this -> span text Buy now-> Thanks for buying
// give the this -> li an class => 'changed'
}
I really tried a lot, f.e with ng-model in the tags, arrays... search the web half the day... but didn't find anything that matches my problem.
Maybe it's just the way of thinking not the angular way... so please help :O)
Kind regard from Hamburg, Germany
Timo
You should be able to do this by changing a property (angular way), no need to access the element in the ng-click handler,and using ng-class and angular binding on that property.
<ul ng-controller="basketCtrl">
<li ng-repeat="item in items" ng-class="{'changed': item.added}">
<button ng-click="addToBasket(item)">
<i ng-class="{'myBasketicon':!item.added,'myOKicon':item.added }">
<span>{{item.added ? "Thanks for buying" : "Buy now"}}</span>
</button>
</li>
</ul>
and in your handler just do:
$scope.addTobasket = function (item){
item.added = true;
}
Most cases, whole purpose of using angular is to avoid DOM manipulation and let angular manage it, you just deal with the models/viewmodels and bindings.
You should add methods for the icon class and text that change their results based on the state of the object, or use custom a custom directive. You definitely don't want to be doing any DOM manipulation (changing text/classes etc) the way you would have done with jQuery.
For the method-based approach, something like this for your markup:
<li ng-repeat="item in item">
<button ng-click="addToBasket(item)">
<i ng-class="getClass(item)">
<span>{{getMessage(item)}}</span>
</button>
</li>
and on your controller:
.controller('ShoppingListCtrl', function($scope) {
$scope.getClass = function(item) {
return item.inBasket ? 'myOkIcon' : 'myBasketIcon';
};
$scope.getMessage = function(item) {
return item.inBasket ? 'Thanks for buying' : 'Buy now';
};
})
This could also be done with a custom directive which is a super powerful way to do things (and definitely worth figuring out) but may be overkill for just starting out. If you find you are adding a lot if methods for doing these sorts of things go with directives.

Conditionally adding data-attribute in Angular directive template

I'm working on the template for a directive. If a property in the scope is set to true, data-toggle="dropdown" should be appended to the element. If the variable is false, this data attribute should not render as an attribute of the element.
For example, if scope variable is true, the template should render:
<span data-toggle="dropdown"></span>
If false, the template should render:
<span></span>
What would the template look like to accomplish this?
For example, I know that I can use ng-class to conditionally include a class. If I want the template to render this:
<span class="dropdown"></span>
Then my template would look like this:
"<span ng-class="{ 'dropdown': isDropDown }"></span>
If scope variable isDropDown is false, then the template will simply render:
<span></span>
So there's a way in a template to conditionally add a class="dropdown". Is there a syntax for templates that allows me to conditionally add data-toggle="dropdown"?
One of the things I've tried for the template is:
"<span data-toggle="{ 'dropdown': isDropDown }"></span>
My thinking with the above template is that if the scope variable isDropDown is true, the value of data-toggle will be set to "dropdown". If isDropDown is false, then the value of data-toggle would simply be an empty string "". That doesn't seem to work though.
I think a good way could be to use ng-attr- followed by the expression you want to evaluate.
In your case it would be something like:
<span ng-attr-data-toggle="{{ isValueTrue ? 'toggle' : 'notToggle' }}"></span>
Here's a fiddle with an example.
<span ng-attr-data-toggle="{{isTrue && 'dropdown' || undefined }}"></span>
will produce when isTrue=true :
<span data-toggle="dropdown"></span>
and when isTrue=false :
<span></span>
At the moment, there is no angular directive that allows you to remove or add an attribute conditionally. You can do ng-switch around the span, one with that attr and another one without it.
<div ng-switch on="condition">
<span data-toggle="dropdown" ng-switch-when="value"></span>
<span ng-switch-default></span>
</div>
or
<span data-toggle="dropdown" ng-if="expression"></span>
<span ng-if="!expression"></span>
You can also create a directive for that same purpose (adding/removing attrs conditionally) but that would be a bit more complicated.
Additionally if what you want is manage the scope variable inside the directive you can pass it as another attribute.
Example:
<span data-toggle="dropdown" when="isDropDown"></span>

Resources