C Ivemy C Ivemy - 10 months ago 59
AngularJS Question

Force splice of ng-repeat array whilst using track by

Our application has a list of tasks, which can grow quite large. The main tasks list has a sidebar, and when a task is selected, it can be edited in the sidebar - which uses a different controller (TasksSidebarCtrl rather than TasksCtrl that displays the list). The selected task is copied, and then merged back into the list. this communication is handled in TasksService.

The number of watchers per task in the index was huge, so we decided to take a different approach.

Using one time binding has greatly reduced to number of watchers (in fact, it's only the submenu to edit/change the status of a task without selecting it in the sidebar that creates watchers now), but because we are using track by task.id (which I see as a necessity considering the number of filters we have and how often the DOM will change), using splice does not remove and replace the task with the new updated task - which I understand is the whole point of track by.

Is there a way to force to update of this task item whist still using both track by and one-time binding?

tasks-index.html (I've removed the filters to aid readability)

<md-list-item my-task-directive ng-repeat="task in TasksCtrl.tasks track by task.id">


TasksService.index().then(function(tasks) {
vm.tasks = tasks.data ? tasks.data : [];

$scope.$on('task-updated', function() {
var newTask = TasksService.getUpdatedTask();
$timeout(function() {
vm.tasks.splice(newTask.index, 1, newTask.task);


$scope.$watch(function() {
return TasksService.getSelectedTask();
}, function (newVal, oldVal) {

if (newVal !== oldVal) {
/* Using copy here so index view does not update as we edit */
vm.selectedTask = angular.copy(newVal);
vm.index = TasksService.getSelectedTaskIndex();


TasksService.update(vm.selectedTask).then(function(data) {
// notifications etc here, then...
TasksService.updateTaskInIndex(vm.index, data.data);


var selectedTask = {};
var indexOfTask;
var updatedTask = {};
var updatedIndex;

this.setSelectedTask = function(task, index) {
selectedTask = task;
indexOfTask = index;
this.getSelectedTask = function() {
return selectedTask;
this.getSelectedTaskIndex = function() {
return indexOfTask;
this.getUpdatedTask = function() {
return {
'index': updatedIndex,
'task': updatedTask
this.updateTaskInIndex = function(index, task) {
updatedTask = task;
updatedIndex = index;

Using a simple angular.copy (with destination) instead of TasksService.updateTaskInIndex(vm.index, data.data) in the TaskSidebarCtrl update function means that the one-time bound properties are not updated in the index view because of the one time binding. The above works without track by task.id but I do't want to make this sacrifice if possible. Nor do I want to sacrifice the one-time binding.

Is there a workaround?

Answer Source

Since the track by task.id gets the same value for the new updated task, it won't rebuild the DOM elements for the entry, and the expressions in this existing element wont update due to one-time binding.

Using a track by expressions that changes when the entry is updated will remove the old task's node and insert a new one, where the one-time bound expressions will be evaluated again. A simple way for this is to assign the task a tracking_id property with an "{id}_{version}" format, where version changes with every update, and use track by task.tracking_id.