JoCa JoCa - 5 months ago 9
Javascript Question

Custom filter on 'ng-repeat' does overwrite the scope

My aim is to replace the teacher-id(f_teacher) of one outputted array with the teacher name of another array. I wrote a custom filter, that should do the job:

angular.module('core')
.filter('replaceId', function () { //filter, which replaces Id's of one array, with corresponding content of another array
return function (t_D, s_D, t_prop, s_prop) { //data of target, data of source, target property, source property
var replacment = {};
var output = [];
angular.forEach(s_D, function (item) {
replacment[item.id] = item[s_prop]; //replacment - object is filled with 'id' as key and corresponding value
});
angular.forEach(t_D, function (item) {
item[t_prop] = replacment[item[t_prop]]; //ids of target data are replaced with matching value
output.push(item);
});
return output;
}
});


I use a 'ng-repeat' like this:

<tr ng-repeat="class in $ctrl.classes | filter:$ctrl.search | replaceId:$ctrl.teachers:'f_teacher':'prename' | orderBy:sortType:sortReverse">
<td>{{class.level}}</td>
<td>{{class.classNR}}</td>
<td>{{class.f_teacher}}</td>
</tr>


But it only outputs an empty column. Now the strange thing: If I follow the steps with the debugger, it works for the first time the filter is performed. But when it is performed a second time it outputs an empty column.

I noticed that the returned object of the filter overwrites the $ctrl.classes - array, but normally this shouldn't be the case?

Here is a plnkr:
https://plnkr.co/edit/EiW59gbcLI5XmHCS6dIs?p=preview

Why is this happening?

Thank you for your time :)

rob rob
Answer

The first time through your filter the code takes the f_teacher id and replaces it with the teacher name. The second time through it tries to do the same thing except now instead of getting a teachers ID in f_teacher it finds the teacher's name so it doesn't work. You could fix it by making a copy of the classes instead of modifying them directly. e.g.

angular.forEach(t_D, function (item) {
    var itemCopy = angular.copy(item);
    itemCopy[t_prop] = replacment[itemCopy[t_prop]];
    output.push(itemCopy);
});

https://plnkr.co/edit/RDvBGITSAis3da6sWnyi?p=preview

EDIT

Original solution will trigger an infinite digest because the filter returns new instances of objects every time it runs which will cause angular to think something has changed and retrigger a digest. Could you just have a getter function that gets a teachers name instead of using a filter?

$scope.getTeacherName = function(id) {
  var matchingTeachers = $scope.teachers.filter(function(teacher) {
    return teacher.id == id;
  })

  //Should always be exactly 1 match.
  return matchingTeachers[0].prename;
};

And then in the HTML you could use it like

<tr ng-repeat="class in classes">
  <td>{{class.level}}</td>
  <td>{{class.classNR}}</td>
  <td>{{getTeacherName(class.f_teacher)}}</td>
</tr>

https://plnkr.co/edit/gtu03gQHlRIMsh9vxr1c?p=preview