Reordering ng-repeat after editing an element - angularjs

Suppose I have an row ng-repeat with orderBy at a certain property/column say name. If I edit a column, it redirects into another page with the fields as editable input. When I edit name and commit the edit, it goes back to the table page, but the edited element stays at the previous position when it should have moved somewhere. So how do I make AngularJS compute orderBy again? Thanks.

You're doing something wrong. Here's a working demo (click on a person to edit their age).
And here's some code needed to post my answer with a link to codepen:
<div ng-repeat="person in people | orderBy:'age'">...</div>

In your view you can do it this way:
<div ng-repeat="item in sortedItems = (allItems | orderBy : itemProperty : reverse)" >
</div>
If you want to to do it from your controller( you have to remove filter from the example above):
$scope.$watch('sortedItems', function() {
$scope.allItems = orderBy($scope.allItems, itemProperty, reverse);
}, true);
The second option will do the job for you, but is definitely not a good practice, and you should avoid it if possible.

Related

Filter list generated with ng-repeat by md-tabs

next question (sorry, I really try to code, but at the moment I don't understand everything).
I have a list with checkboxes which is generated with ng-repeat.
Above I have a tab navigation which I want to use as a filter.
I tried something like this (in many variations) but it doesn't work:
<span ng-repeat="item in items | filter: item.category: cat.category">
My exceptation is that on tab Category1, only bullhorn, car and star are shown, Category2 only euro, eye and facebook and tab Category3 only fax, feed and film are shown.
Now there are all items from the scope, not filtered after category.
Heres a plnkr example with my code: https://plnkr.co/edit/iCQRwH4XRr3DKQcQG329
Many thanks in advance!
What you want is to filter your Array with the cat Object, so you just add the Object witch you want to use to filter (look at the Docmentation > expression > Object):
<span ng-repeat="item in items | filter: cat.category">
Working Plunker

Items disappear when editing inside an Angular ngRepeat

I have an array of items that each display as a directive inside an ng-repeat like this:
<div ng-repeat="item in ctrl.data | filter:ctrl.query" class="ubi-box container-fluid">
<user-item item="item" . . .></user-item>
</div>
As you can see, there is a filter on the ng-repeat. Inside the directives, users can edit items inside forms that show when the user clicks on one of the items.
Trouble occurs when the user edits a field that affects the filter. If the user has found the item using the filter (ctrl.query), and if the edited text means the item no longer matches the filter, that item suddenly vanishes before the user can hit save or anything. Poof!
What is the most elegant solution? I don't want to turn the filter off when editing commences, because then all the items will reappear. I want the filter to still work, but I want the item being edited to keep showing even if it would be filtered out.
Do I set an "isOpen" flag in each item, and add an "or isOpen" clause to the filter? Adding flags like that always feels kludgy to me, and I'm not even sure how to do that in the filter syntax.
Do I add an "or isFormVisible" clause to the filter? That might be neater, but again, how do I even do that in the ngRepeat filter syntax?
Thoughts?
Thanks in advance
John
There might be more elegant solutions out there, but I would probably follow the example from the Angular documentation.
https://docs.angularjs.org/guide/forms#binding-to-form-and-control-state
When editing commences, copy the item being edited to a new object that populates the edit controls (the view controls behind the scenes are still the master object). Once you save, you copy the updated object into the master object which will at that point revert back to the view mode and it'll be hidden by the filter.
Something along these lines:
$scope.edit = function (item){
$scope.editItem = angular.copy(item);
}
$scope.saveTo = function(item){
for (var i = 0; i < $scope.items.length; i++){
if ($scope.items[i].id === item.id){
$scope.items[i] = angular.copy($scope.editItem);
break;
}
}
$scope.editItem = {};
}
I compare the ID in the item being edited to the ids of each item in the list to ensure only one can be ng-if'd at a time. Here's a sample plunk.
Update: Here's another plunk that uses a different query filter and all you do is set a flag when you go into edit mode to an id. Similar to the one before, but none of the master object code. If you already have an edit mode, this seems like it should be pretty quick to apply.
I would suggest using ng-model-options along with $render option, here is a working example : http://plnkr.co/edit/IFpXBYeJx1wrbKhhzMZg?p=preview
<form name="userForm">
<label>Name:
<input type="text" name="name" ng-model="name" ng-model-options="{ updateOn: 'click' }" />
</label>
<button ng-click="userForm.userName.$render();">Update Now</button>
<br />
</form>

How to set a boolean flag to collapse/expand a row with ng-repeat

I have this plunker code.
What I'm trying to do, is to display the gray box one time per row.
To achieve this, I thought to modify the partition filter in order to return a JSON to add it a new property by row to know if the gray box is expanded or not.
But, I could Not successfully return a JSON.
Do you know how to modify the filter to return a JSON or a better way to show the gray box by row?
Related questions:
Push down a series of divs when another div is shown
Update 1
The issue could be easily resolved by using the correct scope for the ng-repeat for the row without modifying the filter, thanks to #m59.
http://plnkr.co/edit/eEMfI1lv6z1MlG7sND6g?p=preview
Update 2
Live Demo
If I try to modify the item, it seems the ng-repeat would be called again losing the props values.
<div ng-repeat="friendRow in friends | partition:2"
ng-init="props = {}">
<div ng-repeat="item in friendRow"
ng-click="collapse(item)"
ng-class="{myArrow: showArrow}">
{{item.name}} {{item.age}} years old.
<div>{{item.name}}</div>
</div>
<div collapse="!props.isExpanded">
some content
<br/>
<input type="text" ng-model="currentItem.name">
</div>
</div>
js
$scope.collapse = function(item){
this.props.isExpanded = !this.props.isExpanded;
this.showArrow = !this.showArrow;
$scope.currentItem = item;
};
This causes the gray box to collapse each time the item is modified. Any clue?
I've updated my code/answer regarding partitioning data. It's important to fully understand all of that before deciding on an approach to your project.
The problem you have in your plnkr demo is that you're modifying the parent $scope and not the scope of the ng-repeat for that row.
Just set a flag on the row and toggle it when clicked:
Live Demo
<div
class="row"
ng-repeat="friendRow in friends | partition:2"
ng-init="isExpanded = false"
ng-click="isExpanded = !isExpanded"
>
<div ng-repeat="item in friendRow">
{{item.name}} {{item.age}} years old.
</div>
<div collapse="!isExpanded">
some content
</div>
</div>
To access the correct scope within a function in the controller, you can use the this keyword instead of $scope. this will refer to the scope the function is called from, whereas $scope refers to the scope attached to the element with ng-controller (a parent of the ng-repeat scopes you want to target).
<div
class="row"
ng-repeat="friendRow in friends | partition:2"
ng-click="collapse()"
>
JS:
$scope.collapse = function() {
this.isExpanded = !this.isExpanded;
};
If you want to keep the ng-click directive on the item element instead of putting it on the row element as I have done, then you're dealing with another child scope because of that inner ng-repeat. Therefore, you will need to follow the "dot" rule so that the child scope can update the parent scope where the collapse directive is. This means you need to nest isExpanded in an object. In this example, I use ng-init="props = {}", and then use props.isExpanded. The dot rule works because the children share the same object reference to props, so the properties are shared rather than just copied, just like in normal JavaScript object references.
Live Demo
<div
class="row"
ng-repeat="friendRow in friends | partition:2"
ng-init="props = {}"
>
<div ng-repeat="item in friendRow" ng-click="collapse()">
{{item.name}} {{item.age}} years old.
</div>
<div collapse="!props.isExpanded">
some content
</div>
</div>
JS:
$scope.collapse = function(){
this.props.isExpanded = !this.props.isExpanded;
};
Update
We keep going through more and more issues with your project. You really just need to experiment/research and understand everything that's going on on a deeper level, or it will just be one question after another. I'll give it one last effort to get you on the right track, but you need to try in the basic concepts and go from there.
You could get past the issue of props reinitializing by putting $scope.expandedStates and then passing the $index of the current ng-repeat to your function (or just using it in the view) and setting a property of expandedStates like $scope.expandedStates[$index] = !$scope.expandedStates[$index]. With the nested ng-repeat as it is, you'll need to do $parent.$index so that you're associating the state with the row rather than the item.
However, you'll then have another problem with the filter: Using my old partition code, the inputs inside the partitions are going to lose focus every time you type a character. Using the new code, the view updates, but the underlying model will not. You could use the partition filter from this answer to solve this, but from my understanding of that code, it could have some unexpected behavior down the road and it also requires passing in this as an argument to the filter. I don't recommend you do this.
Filters are meant to be idempotent, so stabilizing them via some kind of memoization is technically a hack. Some argue you should never do this at all, but I think it's fine. However, you definitely should ONLY do this when it is for display purposes and not for user input! Because you are accepting user input within the partitioned view, I suggest partitioning the data in the controller, then joining it back together either with a watch (continuous) or when you need to submit it.
$scope.partitionedFriends = partitionFilter($scope.friends, 2);
$scope.$watch('partitionedFriends', function(val) {
$scope.friends = [].concat.apply([], val);
}, true); // deep watch

Ambiguity in the use of ngRepeat

I have following problem with using of AngularJS ngRepeat.
The issue can be viewed in this jsFiddle.
http://jsfiddle.net/zono/9rmEs/2/
The user can choose character and after this get all combination
of chosen characters in alphabet. Eg:
A - A-B, A-C, A-D and etc.
B - B-A, B-C, B-D and etc.
Everithing works properly but when user change value of selected
character the combination does not get updated. I solved this problem
with adding following code.
<span style="display: none;">
{{item.combionations = getCombinations(item)}}
</span>
And "hack" it. But there must be normal solution.
I would be very grateful for any ideas and recommendations.
Best regards.
Update
In case you plan to do more complex calculations based on the selection this simplified approach would not work. In general it is also better to encapsulate state in some data structure. In your case you could design a structure like this:
{ letter: "A", combinations: ["A-B", "A-C", ... ] }
To update the combinations array you can use ng-change="updateItem(item)" and some update function. Whenever you change the selection the array combination gets updated:
$scope.updateItem = function(item) {
item.combinations = getCombinations(item.letter);
}
I put this in a new fiddle.
You can easily solve this issue by using the model you bound to ng-select in the ng-repeat.
In the select you used item.model. Angular will update its value in the scope whenever you change the selection.
<select data-ng-model="item.model" ng-init="item.model=allLetters[0]" ng-options="value for value in allLetters">
</select>
When you use the same scope variable in ng-repeat you should get the desired behavior.
<div ng-repeat="letter in allLetters">
{{item.model}}-{{letter}}
</div>
Take a look an the updated fiddle.
The problem is that you compute combionations once at the begenning (ng-init="item.combionations=getCombinations(item)"). After that it never gets updated when you change item.model.
You could solve this problem (and also make sure created[...].combionations is kept up-to-date) like this:
<div data-ng-repeat="item in created">
<div ng-repeat="combination in item.combionations = getCombinations(item)">
{{combination}}
</div>
...
See, also, this short demo.

unshifting to ng-repeat array not working while using orderBy

Here is my problem. I've got a comment roll thats using ng-repeat to display the content of a comment array. When the user submits a comment I wan't to unshift to that array in order to display the most recent comment at the top of the list. This works perfectly, but when I add the orderBy filter to the repeat the new comment is applied to the bottom of the repeat.
Here is the comment array HTML:
<ul ng-repeat="comment in comments | filter:{page_id:raceID} | orderBy:'id':'reverse' track by comment.id ">
<li>{{comment.id}}</li>
<li>{{comment.page_id}}</li>
<li>{{comment.user_name}}</li>
<li>{{comment.comment_copy}}</li>
</ul>
Here is the corresponding Controller:
$scope.comment = new newComments({page_id:3, comment_copy:'test comment copy'});
$scope.comment.$save(function(data) {
$scope.comments.unshift($scope.comment);
console.log($scope.comment.id);
});
.....
I scrapped the
orderBy:'id':'reverse'
and instead used a custom filer left on another post here, Angular ng-repeat in reverse. Here is the custom function
app.filter('reverse', function() {
return function(items) {
return items.slice().reverse();
};
});
Now the most recent comment was still not showing up at the top of the page so I had to change from unshift to push. This worked perfectly. Here's the code:
$scope.comment.$save(function(data) {
$scope.comments.push($scope.comment);
});
A few things are wrong in your original post:
orderBy:'id':'reverse' should be orderBy:'id':reverse, where reverse is a boolean (so either replace it by a variable available on the scope, or by a hard-coded true/false). Currently, it defaults to undefined and is interpreted as false.
In your controller code, the field comment.id is not assigned. It will default to undefined and that's the reason sorting does not work as expected.
Additionally:
Array unshift or push will not make a difference in your use case if you correct the aforementioned and the orderBy function is invoked.
In my experience in Angular 1.5.8, track by $index in fact prevented the orderBy function from working. Replacing it by a unique identifier on the object to be repeated, i.e. track by comment.id is preferred. In terms of performance, they should be analogous. Not using the track by clause is the slowest option though. (https://docs.angularjs.org/api/ng/directive/ngRepeat)
that worked for me, would love to know how it affects performance though:
change:
track by comment.id
to:
track by $index
both unshift() and push() should work now, but again, not sure about how much it slows down the DOM regeneration.

Resources