Can you advice me the right sole of some issue?
I have next situation:
1.I load list of items from server and list it with ng-repeat in container here:
<div layout="column" id={{vm.folderId}}container flex layout-align="start center" class='container' dragula='"first-bag"'>
<div id={{item.id}}child class="md-whiteframe-5dp capitalize itemsInList" layout="column" ng-repeat="item in vm.workItems | filter:search">
<div layout="column" flex >
<div class="workItemName">{{vm.getWorkItemName(item.metadata)}}</div>
<div class="workItemDescription">{{vm.getWorkItemDescription(item.metadata)}}</div>
</div>
</div>
</div>
So lets say we have listed next list:
item_1
item_2
item_3
item_4
than I drop another element into this container, ITEM_5.
After I've droped server gives me new list: item_1 -
item_2 -
item_3 -
item_4 -
item_5
and I update my vm.workItems here:
if (data.ok) {
if (data.content.items.length != vm.workItems.length) {
vm.workItems = data.content.items; //from server
SharedDataService.setLastUpdateTimeStamp(SharedDataService.getNewUpdateTimeStamp());
}
}
After when it ng-repeat fresh items it also view droped element in list.So in the end I have:
item_1 //ng-repeated
item_2 //ng-repeated
item_3 //ng-repeated
item_4 //ng-repeated
item_5 //ng-repeated
item_5 //droped element
How to avoid this? is there some normal solving?
update. I drop element ,when it's droped I make request to server to ad new item to list:
$scope.$on('first-bag.drop', function (e, el, container) {
var elementId = parseInt(el[0].id);
var newContainerId = parseInt(container[0].id);
var newPrevElementId;
var newPrevElement = document.getElementById(el[0].id).previousElementSibling;
if(newPrevElement) {
newPrevElementId = parseInt(newPrevElement.getAttribute('id'));
} else if(!newPrevElement) {
newPrevElementId = newContainerId;
}
if(exContainerId != newContainerId) {
WebSocketService.addItem(elementId, exContainerId,
}
}
You can do
ng-repeat="item in vm.workItems track by item.id | filter:search"
If each item has a unique ID(or something unique property), and if you use track by ID, then even if server returns previous items with a new item added to it DOM will not get re-rendered, only new item will be added to the DOM.
Plunker (take a look at console)
ng-repeat adds $$hashkey property to each object when it repeats through a list of objects.
When your server returns the new data, though there are already existing objects, since, the new data(each object) does not contain $$hashkey property in it, ng-repeat re-renders then thinking they are new objects.
If you use track by ID then ng-repeat does not add a $$hashkey property, it keeps track of each item using ID. So, even if server returns same data again, since, ID's matches, ng-repeat does not repeat them. If there are new ID's then it renders then to the DOM.
Take a look at this blog post.
Try this in your function:
let data = data.content.items;
let temp = [];
data.forEach(function(item) {
if (temp.indexOf(item) === -1) {
temp.push(item);
}
});
vm.workItems = temp;
Hope this helps.
Related
I'm having an issue with ngRepeat :
I want to display a list of students in two different ways. In the first one they are filtered by group, and in the second they are not filtered.
The whole display being quite complex, I use a ngInclude with a template to display each student. I can switch between view by changing bClasseVue, each switch being followed by a $scope.$apply().
<div ng-if="currentCours.classesOfGroup !== undefined"
ng-show="bClassesVue">
<div ng-repeat="group in currentCours.classesOfGroup">
<br>
<h2>Classe : [[group.name]]</h2>
<div class="list-view">
<div class="twelve cell"
ng-repeat="eleve in group.eleves | orderBy:'lastName'"
ng-include="'liste_eleves.html'">
</div>
</div>
</div>
</div>
<div class="list-view" ng-show="!bClassesVue">
<div class="twelve cell"
ng-repeat="eleve in currentCours.eleves.all"
ng-include="'liste_eleves.html'">
</div>
</div>
My problem happens when my list of students change (currentCours here). Instead of refreshing the ngRepeat, both lists concatenate, but only in the unfiltered view.
I tried adding some $scope.$apply in strategic places (and I synchronize my list for example) but it doesn't help.
EDIT : the function used to refresh currentCours in the controller. It's called when a "cours" is selected inside a menu.
$scope.selectCours = function (cours) {
$scope.bClassesVue = false;
$scope.currentCours = cours;
$scope.currentCours.eleves.sync().then(() => {
if ($scope.currentCours.classe.type_groupe === 1) {
let _elevesByGroup = _.groupBy($scope.currentCours.eleves.all, function (oEleve) {
return oEleve.className;
});
$scope.currentCours.classesOfGroup = [];
for(let group in _elevesByGroup) {
$scope.currentCours.classesOfGroup.push({
name: group,
eleves: _elevesByGroup[group]
});
}
$scope.bClassesVue = true;
}
});
utils.safeApply($scope);
};
Well, I found a workaround, but I still don't know why it didn't work, so if someone could write an explanation, I would be very thankful.
My solution was simply to open and close the template each time I switch between views.
I am using the AngularJS UI Sortable directive and I am trying to pull the data from my view into my controller and update/stop the sorting on every click. I am creating a blank array and then attaching the $scope.areas to the blank array. I am able to display the content through the ng-repeat. However, when I console.log(areas), I am getting an undefined.
VIEW
<div class="panel-body">
<ul ui-sortable="sortableOptions" ng-model="areas" class="uk-nestable">
<li data-item="{{area.label}}" data-item-id="{{area.order}}" ng-repeat="area in areas">
<div class="uk-nestable-item mainarea-blue">
<div class="uk-nestable-handle mainarea-text-white"></div>
<div data-nestable-action="toggle"></div>
<div class="list-white">{{area.label}}</div>
</div>
</li>
</ul>
</div>
CONTROLLER
//create a blank array
var tmpList = [];
//attaches ng-model scope to tmpList
$scope.areas = tmpList;
console.log(areas);
//changed old sort to new sort
$scope.sortingLog = [];
$scope.sortableOptions = {
//creates a log entry of the new update view
update: function(e, ui) {
var logEntry = tmpList.map(function(i){
return i.value;
}).join(', ');
//displays the update text and array
$scope.sortingLog.push('Update: ' + logEntry);
},
stop: function(e, ui) {
// this callback has the changed model
var logEntry = tmpList.map(function(i){
return i.value;
}).join(', ');
$scope.sortingLog.push('Stop: ' + logEntry);
}
};
My goal is to display the area.label in the correct sort order. For example; I have 3 unordered lists - floor, basement, kitchen and I want to change the order to basement, floor, kitchen. As I am changing the sort order it is updating and stop the sorts.
I am probably not doing the best job explaining myself so here is a similar codepen...http://codepen.io/thgreasi/pen/jlkhr.
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.
How do you get a single item from a GoInstant GoAngular collection? I am trying to create a typical show or edit screen for a single task, but I cannot get any of the task's data to appear.
Here is my AngularJS controller:
.controller('TaskCtrl', function($scope, $stateParams, $goKey) {
$scope.tasks = $goKey('tasks').$sync();
$scope.tasks.$on('ready', function() {
$scope.task = $scope.tasks.$key($stateParams.taskId);
//$scope.task = $scope.tasks.$key('id-146b1c09a84-000-0'); //I tried this too
});
});
And here is the corresponding AngularJS template:
<div class="card">
<ul class="table-view">
<li class="table-view-cell"><h4>{{ task.name }}</h4></li>
</ul>
</div>
Nothing is rendered with {{ task.name }} or by referencing any of the task's properties. Any help will be greatly appreciated.
You might handle these tasks: (a) retrieving a single item from a collection, and (b) responding to a users direction to change application state differently.
Keep in mind, that a GoAngular model (returned by $sync()) is an object, which in the case of a collection of todos might look something like this:
{
"id-146ce1c6c9e-000-0": { "description": "Destroy the Death Start" },
"id-146ce1c6c9e-000-0": { "description": "Defeat the Emperor" }
}
It will of course, have a number of methods too, those can be easily stripped using the $omit method.
If we wanted to retrieve a single item from a collection that had already been synced, we might do it like this (plunkr):
$scope.todos.$sync();
$scope.todos.$on('ready', function() {
var firstKey = (function (obj) {
for (var firstKey in obj) return firstKey;
})($scope.todos.$omit());
$scope.firstTodo = $scope.todos[firstKey].description;
});
In this example, we synchronize the collection, and once it's ready retrieve the key for the first item in the collection, and assign a reference to that item to $scope.firstTodo.
If we are responding to a users input, we'll need the ID to be passed from the view based on a user's interaction, back to the controller. First we'll update our view:
<li ng-repeat="(id, todo) in todos">
{{ todo.description }}
</li>
Now we know which todo the user want's us to modify, we describe that behavior in our controller:
$scope.todos.$sync();
$scope.whichTask = function(todoId) {
console.log('this one:', $scope.todos[todoId]);
// Remove for fun
$scope.todos.$key(todoId).$remove();
}
Here's a working example: plunkr. Hope this helps :)
I'm trying to build a simple page to group record and then add a button to eliminate some records.
The problem is that the record eliminated that has the same name is deleted from the wrong grouped list. And also if a list have no grouped records should disappear, and instead is always there.
Fiddle: http://jsfiddle.net/Tropicalista/qyb6N/15/
// create a deferred object to be resolved later
var teamsDeferred = $q.defer();
// return a promise. The promise says, "I promise that I'll give you your
// data as soon as I have it (which is when I am resolved)".
$scope.teams = teamsDeferred.promise;
// create a list of unique teams
var uniqueTeams = unique($scope.players, 'team');
// resolve the deferred object with the unique teams
// this will trigger an update on the view
teamsDeferred.resolve(uniqueTeams);
// function that takes an array of objects
// and returns an array of unique valued in the object
// array for a given key.
// this really belongs in a service, not the global window scope
function unique(data, key) {
var result = [];
for (var i = 0; i < data.length; i++) {
var value = data[i][key];
if (result.indexOf(value) == -1) {
result.push(value);
}
}
console.log(result)
console.log(Math.ceil(result.length / 10))
$scope.noOfPages = Math.ceil(result.length / 10);
return result;
}
$scope.currentPage = 1;
$scope.pageSize = 5;
$scope.maxSize = 2;
$scope.deleteItem = function(item){
//code to delete here
var index=$scope.players.indexOf(item)
$scope.players.splice(index,1);
};
Here is a sample of something expanding on the tip from SpykeBytes
<div ng-repeat="location in journey.locations">
<div id="location_div_{{ $index }}">
<label class="journey-label">Location name</label>
<input class="journey-input" id="location_{{ $index }}" type="text" ng-model="location.location_name" />
<button ng-show="editable" tabindex="-1" class="journey-button remove" ng-click="removeItem(journey.locations, $index)">
Remove location
</button>
Then in my controller I set up an action that takes deletes the individual item
$scope.removeItem = function(itemArray, index) {
return itemArray.splice(index, 1);
};
To hide the group when nothing is listed, you need to get the filtered list and then use ng-show to drive the display. This is a bit tricky:
<div ng-show="currentList.length>0" ng-repeat="team in teams| startFrom:(currentPage - 1)*pageSize | limitTo:pageSize | filter:searchInput"> <b>{{team}}</b>
<li ng-repeat="player in (currentList = (players | filter: {team: team}))">{{player.name}}
<button class="btn btn-small" type="button" ng-click="deleteItem(player)">Delete</button>
</li>
</div>
However I am not seeing the problem you said about removing from wrong group. Can you let me know how to reproduce it?
Index won't help you here because the {{$index}} that ng-repeat provides is within the groupings. That is, each grouping restarts the $index variable. You are going to need a unique identifier for each record though. Without that there is no way to be sure that the record you want to remove is the right one.
As far as the groupings, you can recreate the model whenever you delete something. This wouldn't work with the sample data in the Fiddle, but it works when you're dealing with a real datasource.
You can instead pass the index of the object if it is within ng-repeat.