I have a simple list in ui-select, if I choose to delete an item, and load in the ui-select the first element available in the list, the model associated don't get updated. Not sure what I am missing !.
Definition of the ui-select:
<ui-select on-select="loadSelected($item)" ng-model="selectedDude">
<ui-select-match placeholder="{{selectedDude.name}}">
<span> {{selectedDude.name}} </span>
</ui-select-match>
<ui-select-choices repeat="d in data | filter: $select.search">
<span ng-bind-html="d.name | highlight: $select.search"></span>
</ui-select-choices>
</ui-select>
This function is the one I am using for delete:
$scope.deleteSelected= function(){
$scope.data.splice($scope.data.indexOf($scope.selectedDude),1);
$scope.selectedDude = $scope.data[0];
};
Check the example in plunker
Thanks for any help.
I've modified the plunkr for you to get it working. https://plnkr.co/edit/rCKCng6ecXiZ8cNGTBlz?p=preview
First, I added a small utility method in Array to remove an item from a list of objects:
Array.prototype.remove = function(key, value) {
var index = -1;
angular.forEach(this, function(item, i) {
if (item[key] === value) {
index = i;
}
});
if (index > -1) {
this.splice(index, 1);
return true;
}
return false;
};
There were two problems, the first one was related to how you were removing the selectedDude from an array of objects.
$scope.data.splice($scope.data.indexOf($scope.selectedDude), 1);
Since the dude object reference instance stored in the array might be different from what the scope variable selectedDude has. So splice might not work properly all the time as you change anything in it.
So we precisly removing it by searching it through the index (using a utility method).
The second problem was of nested child scope. Read here for more information. We fixed this problem by creating an object dataStore and referencing selectedDude from that object like dataStore.selectedDude to prevent child inheritence problem in Javascript.
Related
actually no biggie but how would a computed property filter function look like that always returns the current array + 5 more elements?
more in detail:
Template:
<span class="box-content" v-for="item in activeItems" :key="item.id">
<img class="item" :src="item.filename" />
</span>
Script
data: function() {
return {
items: [],
limit: 1,
};
},
computed: {
activeItems: function() {
return this.items.filter( function(s) {
if(s.length > this.limit) {
return s;
}
});
// return this.limit ? this.items : this.items;
}
},
on page load , an axios post request gets an object of items, whose response is pushed into the items array which is empty upon component declaration.
so axios -> get object with items -> push into empty array.
now i want to display ,like, 5 items and make a show more button.
The problem now is, my activeItems function is invalid, it does not know "this.limit" and i doubt anyway that it returns the correct result as i just made it return itself and not a set of objects / arrays.
What I would do next is trying around with splice and slice, array copies and pushing elements into it until a certain condition is met but.. is there a better way ?
Thanks in advance
The filter function should be used to filter based on the internal values of an array. Say you have an array of objects with persons, and each Person as an age, then you could use the Array.prototype.filter function to filter based on that age of each entry.
The filter function therefore goes through every entry in your array and determines whether an item should be included or excluded.
If you, on the other hand, want to limit the amount of entries based on a maximum number of entries, I would suggest you use Array.prototype.slice, as you mentioned already.
Your computed function could be rewritten to:
activeItems: function() {
return this.items.slice(0, this.limit)
}
First, in your code, this.limit is undefined because this is referencing the anonymous function. If you want to access the component, you will better use arrow functions syntax.
Also, s references an element of your array, so s.length will be undefined too I guess...
Now, filter does not seem to be the best choice for your need. I'll go with slice instead. Somthing like:
computed: {
activeItems() {
return this.items.splice(0, this.limit)
}
}
Where limit is increased by 5 when you click the show more button.
Of course you could do it. You just missed some code on it. Here how you fix it
activeItems: function() {
let limit = this.limit
return this.items.filter( function(item, s) {
return s <= limit
});
}
If you don't mind using filter, here are some way to do it.
First : put condition in your for loop, this one
<span class="box-content" v-for="(item, index) in items" :key="item.id" v-if="index <= limit">
<img class="item" :src="item.filename" />
</span>
Second is to slice your array on you desired length, this one
<span class="box-content" v-for="(item, index) in items.slice(0, limit)" :key="item.id">
<img class="item" :src="item.filename" />
</span>
Beginner in Angular, so it might sound a little silly question, but couldn't find an answer yet.
I have two select boxes -
one which describes a module which I use as ng-model=module ([x,y,z]).
The second one is an array which in each index I have an array with 3 attributes - id, name and module( [1, "first", x])
I am using ng-repeat for my second select box and I want to filter according to the module and the third index.
Basically, it's something like that: "option in options | filter: module === secondbox[2]", but obviously I'm doing something wrong, maybe by syntax.
Please assist me to execute it right. Thanks!
I think it would be best to write a custom filter for this:
.filter('moduleMatch', function() {
return function(items, module, itemIndex, moduleIndex) {
let out = [];
// make sure a filter value was supplied
if (module) {
items.forEach(i => {
if (i[itemIndex] === module[moduleIndex]) {
out.push(i);
}
});
// return the items that matched the filter value
return out;
}
// no filter value was supplied - return the unfiltered collection
return items;
}
})
Then use it in the second select:
"option in options | moduleMatch: module:2:2"
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 :)
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
I'm working on a data heavy Angular project in which I have a ng-repeat where many values get updated every second. If a value is empty, it now updates the DOM to show an empty value; which is correct behaviour.
Needed solution
What I want is a filter or expression which doesn't update the value in the DOM when the new value is empty or NULL. Latching data, I believe they call it.
Possible solutions
I found a couple of possible solutions with $watch, but I believe they are not suitable in ng-repeat, or at least not efficient:
angularjs $watch old value and new value are the same
Example of what I would like to achieve: (this does not work)
app.filter('latchIt', function () {
return function (valueOld, valueNew) {
if (valueNew == '') {
// Do not update the DOM for this value, perhaps a return of the old value
return valueOld;
} else {
return valueNew;
}
};
});
HTML
<div class="item" ng-repeat="item in items track by item.id">
<div class="value" ng-repeat="value in item.data">{{ value | latchIt }}</div>
</div>
Thanks in advance for any help & advice you can give me.
I'd create a directive for that:
codepen: http://codepen.io/anon/pen/EVXgjz
angular.module('app').directive('latched', [function() {
return {
template: '<span>{{saved}}</span>',
link: function($scope) {
$scope.$watch('value', function(val) {
if (!$scope.saved) {
$scope.saved = val || 'Not defined yet';
}
if (val) {
$scope.saved = val;
}
})
},
scope: {
value: '='
}
}
}])
Well I would say that $watch option is good enough to use it, but if you are agains it you can try to combine old collection values and new collection values according to your business rules in controller/directive and after than pass it to view.
I have a dropdown with values in it. They're accessed through the .value property. I have an ng-repeat on a div that is repeating a lot of data. That data has statuses. When a user selects to filter by a status in the dropdown, I want to filter the ng-repeat by whatever status they chose and the status in the ng-repeat. Here's a better example of what I mean:
data-ng-repeat="stackoverflow in overflows| filter:stackoverflow.property.status===status.value"
In my case, I need to access stackoverflow.property.status and compare it to whatever status is in the drop down.
You'll want to do something like the following:
<span ng-repeat="stackoverflow in overflows | filter: { status : selectedStatus }"></span>
selectedStatus will be the value of ng-model on your dropdown.
You could have something like this
ng-repeat="stackoverflow in overflows | filter: { locations: [{ status: status.value }] }"
Under the circumstances, it ended up just being easier to write my own function and filter by it. In this case, I have a function search that I pass to filter. Works great for me.
I followed an answer that I found on another question. You can view it here: http://jsfiddle.net/suCWn/
$scope.search = function (shop) {
if ($scope.selectedCityId === undefined || $scope.selectedCityId.length === 0) {
return true;
}
var found = false;
angular.forEach(shop.locations, function (location) {
if (location.city_id === parseInt($scope.selectedCityId)) {
found = true;
}
});
return found;
};`
Here's the question: Angularjs filter nested object