Angular removing object from array does not update view - angularjs

Using 1.5.8 other questions haven't provided a solution
I'm displaying images from an array of objects that users can remove by clicking on the image. I want the element in the array to be an empty object after they remove it so there's an empty cell in the view.
<div class="insert-items row m-t-2">
<div class="col-xs text-xs-center slot-item-wrapper" ng-repeat="item in create.availableSlots track by $index">
<div class="slot-item" ng-click="create.removeItemFromSlot(item.id)">
<img ng-src="{{item.img}}" alt="" />
</div>
</div>
</div>
And the JS
function removeItemFromSlot(id){
_.each(create.availableSlots, function(item, i){
if(id === item.id){
create.availableSlots[i] = {};
}
});
}
this does what it intends to... but the item.img is still displaying the old img but there's nothing in the object anymore...
I have a feeling it's because i'm using track by $index and angular can't update the view without it? I have tried using $scope.apply() but it tells me there's already a digest cycle error in the log. Is there a way to update the view using track by here?

Related

Page render speed in AngularJS using ng-include

I've got an issue where I populate a page in pieces. There are an arbitrary number of categories with an arbitrary number of items. The code is generally something like the below (warning, transposed).
$scope.getItems = function(key) {
$http.get('get-items?key=' + key)
.then(function(res) {
for (let item of res.data) {
$scope.categories[item.category].items.push(item);
}
});
}
let populateCategories = function() {
for (let key in $scope.categories) {
$scope.getItems(key);
}
}
$scope.getCategories = function(next) {
$http.get('get-categories')
.then(function(res) {
$scope.categories = res.data;
next();
});
$scope.getCategories(populateCategories);
}
The idea is to first get what categories will be on the page, and render them, empty (but w/ a busy icon). After that, hit and endpoint one time per category and populate w/ the results. The busy icon is shown via ng-show & a boolean pointing to the size of the items. 1 or more items = no busy icon, an the items should show.
The loading of the categories more or less works. Populating them though, is not so free flowing. Watching the console output, it takes ages for the browser to render. The busy icon goes away somewhat quickly, but I don't see the items until a bunch of them are ready.
Worth noting, (I think) I saw this problem appear when I moved the html that displays each item from a single file, an template and used ng-include, as I'm using it on two different places. Surely that would not be a cause would it?
EDIT: Adding the html - simplified
item-template.html
<div class="row">
<div class="col-xs-2 col">
<img src="{{item.img}}">
</div>
<div class="col-xs-10 col">
<div>{{item.details}}</div>
</div>
</div>
list.html
<body>
<div class ="container-fluid">
<div class="row">
<div ng-repeat="(key, value) in categories">
<div>{{key}}</div>
<div ng-show="value.busy"">
<img ng-src="{{busy_image}}">
</div>
<div ng-repeat="item in value.items track by $index">
<!-- This in fact seems to be the culprit -->
<div ng-include="item-template.html">
</div>
</div>
</div>
</div>
</body>
So, playing around, if I simply paste the contents of template.html into list.html, the response is much, much better. Looking at this issue, the solution seems to be to use a cache service. I'm happy to use something like that but I'm still curious as to why. The template I'm using isn't small (166 lines) but I can't imagine it being that heavy either on a modern computer.
Several things from the top of head:
amount of items to be shown in the HTML. Large lists with x properties = alot of Angular watchers.
if there are alot of items, maybe check for an alternative to ng-repeat
instead of ng-include item-template.html, create a Component
ng-repeat with track by
use bind once
in this case you can replace ng-show with ng-if

How to create an independent indexer with default value inside ngRepeat loop

I have a list of posts with comments. I need to traverse through array of comments inside each of the posts on my page. I'm trying to create variable inside ngRepeat for posts which I can use like an indexer to display exact comment for each post. Due to ngRepeat creating nested scope, this variable must be unique for each iteration. But when I'm trying to change it with ng-click, it doesn't change.
My ngRepeat:
<div class="question_block col-xs-12"
ng-repeat="answer in question.content.answer track by answer.id">
is followed by <span style="display:none">{{counter=0}}</span>. And then I'm showing some items like <span>{{answer.comments[counter].user.organization.title}}</span>. When I'm trying to do something like <a href ng-click="counter++">Increment</a> nothing happens. What's the matter ?
Use ng-init="counter = 0" and attach your ng-click to a function in your controller:
<div ng-repeat="item in items track by $index" ng-init="counter=0">
{{item}} ({{counter}})
<button ng-click="clickHandler()">Increment</button>
<hr />
</div>
Then, to increment counter in the context of the event, use this.counter
$scope.clickHandler = function() {
this.counter++;
};
Plunker demo : http://plnkr.co/edit/J4d0JlJSKD7i4OfMT2r4?p=preview

Removing element from ng-repeat does not allow to remove or hide element from DOM

I have below code to display records using ng-repeat:
<div class="gallery">
<div ng-cloak
ng-repeat="photo in photos| orderBy:'-id'"
ng-mouseLeave = "delete_btn = !delete_btn"
ng-mouseEnter = "delete_btn = !delete_btn"
class="gallery_block"
id="photo_block_[[photo.id]]">
<span title="delete photo" ng-show="delete_btn" class="delete_btn_span" rel="[[photo.id]]" id="delete_photo_[[photo.id]]">
<img src="{{asset('frontend/images/cross_icon.png')}}">
</span>
<p>
<a href="javascript:;">
<img ng-click="showImagePopup([[photo.path_popup_thumbnail]]);" src={{$public_path}}./image.php?width=149&height=109&cropratio=2:1.4&image=[[photo.path_popup_thumbnail]] alt="">
</a>
{{--<span> </span>--}}
</p>
</div>
</div>
I have to delete records for that I first have to call a function using $http and then I remove element from photos array:
var index = $scope.photos.indexOf($('#photo_block_'+id));
$scope.photos.splice(index, 1);
Then I remove element from DOM:
$('#photo_block_'+id).remove();
Update
I have called deletePhoto function using jqyery like below:
$(".gallery").on('click','span.delete_btn_span',function()
{
$scope.deletePhoto($(elem), $(elem).attr('rel'));
});
deletePhoto function further do all the work of deleting elements etc.
But it does not remove element. Where as all the selectors are fine.
Is removing element from photos array causing problem?
With angular JS, you have to deal with data. So you should put an ng-click="delete(photo)" on your span.
and create a method in your controller :
$scope.delete = function(photo) {
// delete froms scope.photos here...
}
to delete the photo from the list.
Your code does not work because $scope.photos.indexOf($('#photo_block_'+id)) surely return -1, because you're looking for $('#photo_block_'+id), which is a jquery object in a list of simple json object (i suppose).
So the splice method does anything, and the $scope.photos list remain unchanged, so the view is not changed, because it's build from the list by the ng-repeat directive.
ps: you should never manipulate dom element from controller, if you need to do that, you should create directive.

Trigger a click on first item of ng-repeat, initially and each time new data is loaded

I'm very very new to Angular JS and have the following requirement:
I have an ng-repeat as shown below:
<div class="panel" ng-repeat="(appname, value) in chart.accordionData" style="margin-top: 0;">
<div class="accordion collapsed" data-parent="#accordion1"
data-toggle="collapse"
data-target="#{{appname.replace(' ','')}}">
<div class="accordion-head" initial-select index="{{$index}}">
<div class="arrow chevron"></div>
<h4><i></i>{{appname}}</h4>
</div>
</div>
<div class="accordion-body collapse" id="{{appname.replace(' ','')}}">
<p class="highlightable" >
Some data
</p>
</div>
</div>
The data in this ng-repeat comes from the server.
So the problem statement I have is to perform a click trigger on the first element under ng-repeat, .accordion-head so that the first item in the list is always open. I tried various approaches of putting $watch etc. but when the number of items in the list are same, then the trigger doesn't fire.
(the first item in list has to be clicked even when new data is loaded)
I thought of writing a directive initial-select and perform click based on index but that happens only once. I really need an experts advice.
Any solutions?
I think the simplest option is to use data-ng-init="collapse = !$first;".
Another option:
Have you used Angular UI Bootstrap? Also, are you using $http or $resource to get data from the server?
If you use $resource, you can set a variable on the success callback (interceptor response) to open=true. Then, you can either bind this variable to Angular UI Bootstrap is-open attribute or to your data-toggle. Also, with $resource you can set data-ng-if="chart.accordionData.$resolved" (or chart.$resolved depending on your setup), which is nice.
You can use a watch to watch the array contents.
Check out the following fiddle:
http://jsfiddle.net/akonchady/w0qdsen7/3/
I have used a variable to keep track when the array content changes since directly watching the array will be more memory intensive.
Here is the main code that watches the variable:
$scope.$watch('arrModified', function(newValue, oldValue) {
if(newValue) { //array is modified
//Trigger ng-repeat div click
setTimeout(function() {
$('.item:first').trigger('click');
$scope.arrModified = false;
$scope.$digest();
}, 0);
}
});

ng-click showing all the hidden text field on ng-repeat rather than one in Angular

I started to work with Angular, it's pretty good to implement, I stuck with a single issue at ng-click
I am getting data dynamically and showing with ng-repeat, and I want to update the data at pencil click and for it I am using input text element, but when I click on pencil It's opening all the text fields
Here is my HTML code
<
div ng-repeat="item in scroller.items track by $index">
<div class="secHead text-center">
<button class="common btnDarkGrey" data-ng-hide="hideCatButton">{{item.category_name}}</button>
<input type="text" id="focus-{{$index}}" class="common btnDarkGrey editDashboardCategory" name="editCategory" value="" data-ng-model="item.category_name" data-ng-show="hideField">
<span data-ng-click="updateCategory(item.category_id,item.category_name,$index)" class="chkOneDone" data-ng-show="hideOkButton">Done</span>
<div class="pull-right">
</div>
</div>
</div>
And here I Angular code
$scope.updateCategory=function(category_id,updated_cat_name, $index){
Category.updateCat($rootScope,$scope,$index,$http,$timeout,updated_cat_name,old_cat_name,category_id);
};
$scope.updatePen=function($index){
old_cat_name=$scope.scroller.items[$index].category_name
$scope.hideField=true;
$rootScope.hideOkButton=true;
$rootScope.hideCatButton=true;
};
I created a Category service to perform task like update
I didn't get any proper solution yet.
Can anybody help me?
Thank You.
If you only want to hide/show one of the elements in the list you need to specify that in some fashion. Right now you have a three rootScope booleans:
$scope.hideField=true;
$rootScope.hideOkButton=true;
$rootScope.hideCatButton=true;
being set for the entire list, and you need to set a show properties on each individual in the list.
In your controller function you can do something like this before you expect a click:
//normal for loop so that you have the index
for(var i=0; i < $scope.scroller.items.length; i++){
$scope.scroller.items[i].show = false;
}
Then you can do something like this to actually show the fields:
HTML:
div ng-repeat="item in scroller.items track by $index">
<div class="secHead text-center">
<button class="common btnDarkGrey" ng-hide="!item.show">
{{item.category_name}}</button>
<input type="text" id="focus-{{$index}}" class="common btnDarkGrey editDashboardCategory" name="editCategory" value="" ng-model="item.category_name" ng-hide="!item.show">
<span data-ng-click="updateCategory(item.category_id,item.category_name,$index)" class="chkOneDone" ng-show="item.show">Done</span>
<div class="pull-right">
</div>
</div>
</div>
Controller:
//controller declaration --
$scope.updatePen = function(index){
$scope.scroller.items[index].show = true;
};
It's my understanding that you need all three properties to show once a click happens, so I condensed all the show properties into one single show property.
Your view only sees that hideField is true and performs that action for all of the items in your array. I hope this helps!

Resources