I have an array of items [Expression, Expression,....] and I add/remove Expression's from the array, but ngRepeat doesn't $compile the directive correctly.
What could be the reason for it?
One of the solutions is, that I have a separate array of Expressions, and I modify that one, and every time it changes I reset the "expressions" to [] and set the new array, but when doing this there is blinking of the UI elements (first digest to [] and then timeout(expressions = new array)).
// Main
scope.list = [Expressions.new(), Expressions.new()];
//constructor Expressions
function Expressions(){};
Expression.prototype = {
id: 'generated',
list: [
Expression.new(), Expression.new(),...
]
};
//constructor Expression
function Expression(){};
Expression.prototype = {
id: 'generated',
val: ''
};
<!-- Main Usage -->
<div ng-repeat="expressions in list track by $index" dir-expressions x-attrs="expressions"></div>
<!-- Expressions directive -->
<div class="Expressions_Content">
<ul ng-sortable="attrs.sortable">
<li ng-repeat="expression in attrs.items track by $index">
<div dir-expression x-attrs="expression"></div>
</li>
</ul>
</div>
<!-- Expression directive -->
<div>{{Value}}</div>
It is a complex situation, so I will just put some code snippets in the example.
As Sunil D pointed out, use track by expression.id instead of $index.
Related
Hi I am trying to dynamically add a class to an element that is updated on the view.
http://jsfiddle.net/9s1rfwa8/7/
<div ng-app>
<div ng-controller="TodoCtrl">
<ul>
<li ng-repeat="todo in todos" class="bigfont todo done-{{todo.done}}"> <span>{{todo.text}}</span>
</li>
</ul>
</div>
function TodoCtrl($scope) {
$scope.todos = [{
text: 'learn angular',
done: true
}, {
text: 'build a demo angular appsdfsdfsf',
done: false
}, {
text: 'build an awesome angular app',
done: false
}];
}
How do I add a class to the ng-repeat element that is only changed and not to others.
I am trying to give it a flip effect to the elements that changes.
Ng-repeat will duplicate element that is repeated. If you want to apply class to only one of the element repeated, then you need to add a condition e.g. $index === 0 to ng-class.
ng-class={'first-item-class': $index === 0}
this will apply to the first element only. Use any kind of condition logic to identify its the one you want to apply to.
You can use a custom filter for this, custom filter will be executed whenever there is a change.
For example you can change done property inside your todo object every time user clicks on the row and use the value of todo.done to set class inside your filter
html
<li ng-repeat="todo in todos | filter:handleChange"
class="bigfont todo done-{{todo.done}} {{todo.class}}"
ng-click="todo.done = !todo.done">
<span>{{todo.text}}</span>{{todo.class}}
</li>
js
$scope.handleChange = function(row){
if(!row.done){
row.class = '';
}
else{
row.class = 'changed'
}
return true;
}
this should give you a general idea on how to approach your problem, you can find a working sample in the following plunker.
Demo
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.
I am trying to set the position property on an object that is inside an array being used by ng-repeat.
<div ng-repeat="item in items track by $index" ng-init="pos = $index + 1; item.position = pos">
<span>{{pos}}</span>
</div>
Initially it sets the property fine, however, anytime the HTML is recompiled after a model (I add/remove/move elements in the array) change the pos variable is set properly and correct position is displayed. However it will not update item.position !
Interesting hack:
<li ng-repeat="item in items track by $index">
{{item.position=$index+1}}
<span>{{item.position}}</span>
</li>
Wouldn't suggest it in production though
DEMO
The assignment of properties really doesn't belong in your template. You should ideally be assigning the position property in your controller. You could use something like this in your controller
$scope.$watchCollection('items', function() {
angular.forEach($scope.items, function(item, idx) {
item.position = idx + 1;
});
})
however I'd really question the need to do so. The item's position in the array provides all the information you need.
Demo ~ http://plnkr.co/edit/H5JbsABEdceSXENFFBBO?p=preview
I am trying to pull all items from my array called 'collections'. When I input the call in my html I see only the complete code for the first item in the array on my page. I am trying to only pull in the 'edm.preview' which is the image of the item. I am using Angular Firebase and an api called Europeana. My app allows the user to search and pick which images they like and save them to that specific user.
here is the js:
$scope.users = fbutil.syncArray('users');
$scope.users.currentUser.collections = fbutil.syncArray('collections');
$scope.addSomething = function (something) {
var ref = fbutil.ref('users');
ref.child($rootScope.currentUser.uid).child('collections').push(something);
}
$scope.addSomething = function(item) {
if( newColItem ) {
// push a message to the end of the array
$scope.collections.$add(newColItem)
// display any errors
.catch(alert);
}
};
and the html:
<ul id="collections" ng-repeat="item in collections">
<li ng-repeat="item in collections">{{item.edmPreview}}</li>
</ul>
First, remove the outer ng-repeat. You want to only add the ng-repeat directive to the element which is being repeated, in this case <li>.
Second, from your AngularJS code, it looks like you want to loop over users.currentUser.collections and not just collections:
<ul id="collections">
<li ng-repeat="item in users.currentUser.collections">{{item.edmPreview}}</li>
</ul>
And third, you're defining the function $scope.addSomething twice in your JavaScript code. Right now, the second function definition (which, incidentally, should be changed to update $scope.users.currentUser.collections as well) will completely replace the first.
I'm trying to update a $scope object's array that is displayed in a ng-repeat on the page using input elements containing the array's contents. The plunker example can be found here: Plunker demo (Basic, stripped down example of my problem)
I have the following settings object defined:
$scope.settings = {
list: ['list item one', 'list item two', 'list item three']
};
and I am representing the data on the page like so:
<ul>
<li ng-repeat="item in settings.list">
<input type="text"
value="{{item}}"
ng-model="singleItem"
ng-change="settings.list[$index] = singleItem" />
delete
</li>
</ul>
My goal is to initially populate the <input> fields with the contents of $scope.settings.list and whenever an item is changed update the array, but I haven't figured out how to in the view. Omitting the ng-model and ng-change on the input properly renders the input vale in the text boxes, but then the array isn't modified when changes are made.
Side Note:
In the Plunker example I have $watch on the settings object. In my actual code, this is used to update a "settings cookie" using the $cookies module. Cookies have been omitted in the example, but for debugging purposes I have left the watch in.
There are two primary problems with your approach. The first is that ngRepeat uses an inherited scope, so primitive values (like strings and numbers) don't play nicely. You should pass arrays of objects to ngRepeat rather than arrays of primitives. Your second problem is the over-complicated way of of binding to an input. All you need is to this:
$scope.settings = {
list: [
{ val: 'list item one'},
{ val: 'list item two'},
{ val: 'list item three'}
]
};
And then in your view:
<ul>
<li ng-repeat="item in settings.list">
<input type="text" ng-model="item.val"></input>
<a ng-click="remove($index)">delete</a>
</li>
</ul>
Here's a revised plunker: http://plnkr.co/edit/ZGFjBnVSwM4hNSgVSOCW?p=preview .