AngularJS watch array of objects with index - javascript

I have a question about Angular watch within an array of objects.
I have an array $scope.chartSeries with objects as following:
[{"location": {values}, "id":"serie-1", "meter":{values}, "name": "seriename", "data":[{1,2,4,5,7,4,6}]}]
This is used to draw a linechart with highcharts.
I want to watch this array, and if a value changes I want to know the index and the value that is being changed.
I found several options for watch but none of them seem to fit my situation. Can you help me out?

If you render and change your array in ng-repeat, you can use ng-change directive and pass in it a $index parameter.
For example:
<div ng-repeat="item in array">
<input type="text" ng-model="item.location" ng-change="changeValue($index)"/>
</div>
Or you can use $watch and work with newValue, oldValue parameters
$scope.$watch('array', function (newValue, oldValue) {
for(var i = 0; i < newValue.length; i++) {
if(newValue[i].location != oldValue[i].location)
var indexOfChangedItem = i;
//bla-bla-bla
}
}, true);

You can use $watchGroup(watchExpressions, listener).
For e.g.:
$scope.chartSeries = [{"location": {values}, "id":"serie-1", "meter":{values}, "name": "seriename", "data":[{1,2,4,5,7,4,6}]}];
$scope.$watchCollection('chartSeries ', randomFunction(var) {
//code
});
Or you can use watch for individual values in array to know which ones got changed.

Related

Filtering observable array with knockout

Could you please find what I'm doing wrong with this array filter. Fiddle Here
I've been working on it, and making very slow progress. I checked on a lot of samples but not able to find my issue.
Thanks
//This is the part I'm not able to fix
self.filteredPlaces = ko.computed(function() {
var filter = self.filter().toLowerCase();
if (!filter) {
ko.utils.arrayForEach(self.placeList(), function (item) {
});
return self.placeList();
} else {
return ko.utils.arrayFilter(self.placeList(), function(item) {
var result = (item.city().toLowerCase().search(filter) >= 0);
return result;
});
}
});
You did not data-bind filter to any input. You used query instead.
Change your filter value to use the query observable:
var filter = self.query().toLowerCase();
I think I know what you're trying to accomplish so I'll take a shot. There are a few things wrong with this code.
foreach in knockout accepts an array not a function.
http://knockoutjs.com/documentation/foreach-binding.html
I think you're trying to hide entries that don't contain the text in the search box. For that you need the visible binding. I re-factored your code to the sample below.
<div data-bind="foreach: placeList" class="alignTextCenter">
<p href="#" class="whiteFont" data-bind="text: city, visible: isVisible"></p>
</div>
I added isVisible as an item in your array, and an observable in your class.
var initialPlaces = [
{"city":"Real de Asientos","lat":22.2384759,"lng":-102.089015599999,isVisible:true},
{"city":"Todos Santos","lat":23.4463619,"lng":-110.226510099999,isVisible:true},
{"city":"Palizada","lat":18.2545777,"lng":-92.0914798999999,isVisible:true},
{"city":"Parras de la Fuente","lat":25.4492883,"lng":-102.1747077,isVisible:true},
{"city":"Comala","lat":19.3190634,"lng":-103.7549847,isVisible:true},
];
var Place = function(data) {
this.city = ko.observable(data.city);
this.lat = ko.observable(data.lat);
this.lng = ko.observable(data.lng);
this.isVisible = ko.observable(data.isVisible);
};
Lastly, you want to subscribe to the changes of "query" since it's on your text box so that the list updates when the text box changes. It's the self.query.subscribe line in my fiddle. I apologize about the formatting, I tried several times and could not get it to work.
Working fiddle here

Change entire object in ng-repeat function AngularJs

Hello I have a questions on ng-repeat on Angularand function for change value.
I have this ng-repeat that cycling a ObjectArray and have a button for reset value.
<div ng-repeat="element in data.elements">
<button ng-click="reset(element)" >reset</button>
</div>
Where data.elements is array of objects example:
[{id:1, name:"element1"},{id:2, name : "element2"}];
In my Controller I set function Reset in $scope that should do a copy of object passed to an default object:
$scope.reset = function(el){
$scope.defaultObject = {id:500, name:"default"};
el = angular.copy($scope.defaultObject);
}
But doesn't work, but if I do:
$scope.reset = function(el){
$scope.defaultObject = {id:500, name:"default"};
el.name = $scope.defaultObject.name;
}
It work.
So I would like that when I do (in this example):
el = angular.copy($scope.defaultObject);
have the object el equals to object $scope.defaultObject my question is, Can i copy entire object without cycling all properties?
You're passing an object to the reset function, then you're overwriting this object. That's it, nothing happens because it won't affect the original object, which is in the data.elements array.
You need to use a different approach. Track the element by its index :
<div ng-repeat="element in data.elements track by index">
<button ng-click="reset(index)" >reset</button>
</div>
...then amend data.elements[index]:
$scope.reset = function(index){
$scope.data.elements[index] = {id:500, name:"default"};
}
are you saying, your updated object does not reflect on the UI? you can try forcing the scope update by running $scope.$apply() after you have assigned the object.

Angular: $scope.$watch a nested collection

In my Angular app, I have a checkbox list which is generated via a nested ng-repeat, like so:
<div ng-repeat="type in boundaryPartners">
<div class="row">
<div class="col-xs-12 highlight top-pad">
<div ng-repeat="partner in type.partners" class="highlight">
<label class="checkbox-inline">
<input type="checkbox" value="partner"
ng-model="ids[$parent.$index][$index]"
ng-true-value="{{partner}}"
ng-false-value="{{undefined}}">
<p><span ></span>{{partner.name}}<p>
</label>
</div>
</div>
</div>
</div>
and in my controller:
$scope.ids = [];
$scope.$watchCollection('ids', function(newVal) {
for (var i = 0, j = newVal.length; i < j; i++) {
// Create new participatingPatners tier if it doesn't exist
if(!$scope.report.participatingPartners[i]) $scope.report.participatingPartners[i] = {};
// Give it an id
$scope.report.participatingPartners[i].id = i + 1;
// Map our values to it
$scope.report.participatingPartners[i].entities = $.map(newVal[i], function(value, index) {
return [value];
});
}
});
The problem is, this $scope.$watchCollection stops watching once I've added one of each top-level ids, so if I add a given number of inputs from the first nested list, then another from the second list, My $scope.report.participatingPartners object never gets updated.
How can I $watch for changes within ids[$parent.$index][$index], making sure updated my object whenever a checkbox gets ticket or unticked?
You are creating an array of arrays:
$scope.ids = [
[],
[],
//...
]
But use $watchCollection to watch for changes in the outer array, i.e. of $scope.ids. This will only identify changes when nested arrays become different objects (or created the first time).
You could use $scope.$watch("ids", function(){}, true) - with true standing for "deep-watch", but that would be very wasteful, since it's an expensive check that would be performed on every digest cycle, whether a checkbox was clicked or not.
Instead, use ng-change to trigger the handler:
<input type="checkbox" value="partner"
ng-model="ids[$parent.$index][$index]"
ng-change="handleCheckboxChanged()">
$scope.handleCheckboxChanged = function(){
// whatever you wanted to do before in the handler of $watchCollection
}
$watchCollection is similar to $watch in that it checks the physical object reference, but goes one step further; it also goes one level deep and does a reference check on those properties.
You'll need to use $watch, but set the objectEquality flag to true. This will tell $watch to perform deep reference checking. Depending on the depth of the item being watched this can hurt performance significantly.
$watch(watchExpression, listener, [objectEquality]);
Can you try to watch for object equality :
$scope.$watchCollection('ids', function(newVal) {
}, true);

Update Position Of Observable Array With New Item In Knockout.js

I have a situation where I need to replace a certain item in an observable array at a certain position of it. Right now I am doing it below with the slice method. Is there a better way that is built in to knockout.js to do this at a certain position? I was even thinking about doing a push, and then do a sort on that row with a order property but I have lots of rows and thought that was to much.
var position = ko.utils.arrayIndexOf(self.list(), game);
if (position != -1) {
self.list.remove(self.game);
self.list.splice(position, 0, newGame);
}
Code With Replace, Trying To Update Property Matchup That Has A New Property Called Name
var game = self.game;
if (game) {
var position = ko.utils.arrayIndexOf(self.list(), game);
if (position != -1) {
if (game.Matchup) {
game.Matchup = new Matchup(response.Data);
game.Modified(true);
}
else if (self.game) {
game = new Matchup(response.Data);
}
self.list.replace(self.list()[position], game);
}
}
HTML
<!-- ko foreach: Games -->
<td class="item-container draggable-item-container clearfix">
<div class="item clearfix draggable-active draggable-item" data-bind="draggableCss: { disabled: $data.Disabled(), matchup: $data.Matchup }, draggableGameHandler : { disabled: !$data.Matchup, disabledDrop: $data.Disabled() }, delegatedClick: $root.members.eventSchedule.editGame.open.bind($data, true, ($data.Matchup && $data.Matchup.Type == '#((int)ScheduleType.Pool)'), $parent.Games)">
<span data-bind="if: $data.Matchup">
<span data-bind="attr: { title: Matchup.Title }"><span data-bind="html: Matchup.Name"></span></span>
</span>
</div>
</td>
<!-- /ko -->
data-bind="html: Matchup.Name" doesn't update with replace.
Replacing an item in an observable array
The replace method is one option for replacing an item in an observable array. In your case, you could call it like this:
list.replace(game, newGame);
Bindings update when an observable dependency changes
But your question isn't only about replacing an item in an array. You've stated that the binding html: Matchup.Name isn't updated, so let's look at what could cause it to update:
If Name is an observable, modifying Name will cause an update.
If Matchup is an observable, modifying it will cause an update, but then you'd have to bind it like Matchup().Name and update it like game.Matchup(Matchup(response.Data));.
Replacing the entry in the observable array (is it Games or list?) with a new object will cause the whole inner template to re-render, obviously replacing each binding.
Looking through your code, I can see that in one case (if (game.Matchup)), none of these three things happen, and thus there's no way Knockout can know to update the binding. The first two obviously aren't occurring and although you do call replace on the array, it's the equivalent of this:
list.replace(game, game); // replace the item with itself
The foreach binding doesn't see the above as a change and doesn't update anything. So, to update the binding, you need to make a real update to an observable.
Further comments
To get the index of an item in an observable array, use the indexOf method:
var position = self.list.indexOf(game);
To replace an item at a specific index, use the splice method with a second parameter of 1:
self.list.splice(position, 1 /*how many to remove*/, newGame);
Observable arrays have a built in .replace method you can use to do this:
var position = ko.utils.arrayIndexOf(self.list(), game);
self.list.replace(self.list()[position], game);

AngularJS Filtering by an arbitrary number of filters

I have a search box that takes a series of terms, separated by spaces, that are split into an array using string.split(' '). Is there a way to apply an arbitrary and potentially large number of filters from that array?
Similar to Thad, but actually creating a custom filter module rather than cluttering up the controller. This is how it has worked for me:
<input type="text" ng-model="query">
<li ng-repeat="object in objects | filterFunction:query"> {{object.state}}
.filter('filterFunction', function() {
return function (objects, query) {
// objects is the array being filtered
// query is the value you passed in
array = query.split(' ');
for (var i = 0, len = objects.length; i < len; i++) {
// filter the crap out of these objects
}
}
});
I think that this is where you will need to use a predicate function filter instead of a string filter...
like this (pseudocode):
<!--html-->
<ul>
<li ng-repeat="object in objects | filter:filterFunction">
{{object.description}}
</li>
</ul>
<!-- controller -->
$scope.objects = [ your data here ];
$scope.filterFunction = function(searchVal) {
// write code that returns true or false depending
// on whether or not after you split searchVal any
// of the elements are in your data
}
I haven't tried this, but this is how the documentation says to do it.
-Thad
great multi-filter here:
https://gist.github.com/i8ramin/5825377
Angular JS filter that behaves much like the built in filter one, but allows you to filter with multiple values, space separated. For example "big orange round"

Categories

Resources