How to set knockout variable from array element - arrays

I´m having some finger trouble with this:
<p data-bind="text: $root.myArray()[0]"></p>
<hr>
myVal = <span data-bind="text: $root.myVal()></span>
In viewmodel:
self.myArray = ko.computed(function() {
var categories = ko.utils.arrayMap(self.selectedItems(), function(item) {
return item.id();
});
return categories.sort();
});
self.myVal = ko.observable(self.myArray()[0]);
Printing myArray shows the correct value, but myVal is blank. Why?
(and yes, I only want the first value in the array).
Also, I´d like it as a number when I save to database. Do I need to do some kind of typecast then?

The difference is probably that you are setting the value of myVal to the first object in the array before the array is actually populated. To see this if you console.log(self.myArray()[0]) right before self.myVal is set you should see what is being set. Since you are setting it only once it will not subscribe to change in the array. If you wanted to do this you would use a computed.
self.myVal = ko.computed(function () { return self.myArray()[0]; });
The computed will now fire whenever anything is added to or removed from myArray

Related

Splice object from array

I get array Of data from API and I assign it to two arrays. One of them I bind on it to view checkbox and when I unchecked checkbox I need to splice this object from the second array , but it splice from the two arrays
<div class="row varibles-box" >
<div *ngFor="let variant of variantDetails">
<mat-checkbox (change)="unCheckVariant(variant,$event)" class="example-margin col-4" [checked]="true">
{{variant.englishName}}
</mat-checkbox>
</div>
</div>
this.addProductService.addCustomProducts(this.unitsForm.value).subscribe((res: any) => {
this.variantDetails = res.data ;
this.VariantToSend = res.data;
this.dataSource = new MatTableDataSource(res.data);
console.log(this.VariantToSend)
console.log(this.variantDetails)
});
onCheckVariant(data, event) {
if (!event.checked) {
this.VariantToSend.splice(data, 1)
console.log(this.variantDetails)
console.log(this.VariantToSend)
} else {
this.VariantToSend.push(data)
}
this.dataSource = new MatTableDataSource(this.VariantToSend);
};
Be aware that arrays are "stored" as references in variables.
This means when you write
this.variantDetails = res.data ;
this.VariantToSend = res.data;
Then both your local variables are pointing to the same array. As a result a change to one of them, will effect the other directly.
You could for example use the spread operator.
this.variantDetails = res.data ;
this.VariantToSend = [...res.data];
This solution will create a new array with the same content for the second local variable. But be aware "same content" should be treated as such. If the original array contains objects or arrays (for both they are stored per reference and not per value) you will have the same problem one level deeper. That means if you would change an attribute of a object in the array, then this attribute for this object would ALSO be changed in the other arrays. Because both share the same reference.
A different solution would be not to use "splice" (which changes the array directly) but to use "slice" (if possible) , which will return a new array and leave the original unchanged.
Even there is two arrays but they are pointing to the same reference so whenever u u made change one array it will automatically affect another one.use slice() it returns a new array and leaves the original as it is.
<div class="row varibles-box" >
<div *ngFor="let variant of variantDetails">
<mat-checkbox (change)="unCheckVariant(variant,$event)" class="example-margin col-4" [checked]="true">
{{variant.englishName}}
</mat-checkbox>
</div>
</div>
this.addProductService.addCustomProducts(this.unitsForm.value).subscribe((res: any) => {
this.variantDetails = res.data ;
this.VariantToSend = this.variantDetails.slice();
this.dataSource = new MatTableDataSource(res.data);
console.log(this.VariantToSend)
console.log(this.variantDetails)
});
onCheckVariant(data, event) {
if (!event.checked) {
this.VariantToSend.splice(data, 1)
console.log(this.variantDetails)
console.log(this.VariantToSend)
} else {
this.VariantToSend.push(data)
}
this.dataSource = new MatTableDataSource(this.VariantToSend);
};

Modifying object in array that is being iterated over by ng-repeat from ng-init directive

I am trying to set the position property on an object that is inside an array being used by ng-repeat.
<div ng-repeat="item in items track by $index" ng-init="pos = $index + 1; item.position = pos">
<span>{{pos}}</span>
</div>
Initially it sets the property fine, however, anytime the HTML is recompiled after a model (I add/remove/move elements in the array) change the pos variable is set properly and correct position is displayed. However it will not update item.position !
Interesting hack:
<li ng-repeat="item in items track by $index">
{{item.position=$index+1}}
<span>{{item.position}}</span>
</li>
Wouldn't suggest it in production though
DEMO
The assignment of properties really doesn't belong in your template. You should ideally be assigning the position property in your controller. You could use something like this in your controller
$scope.$watchCollection('items', function() {
angular.forEach($scope.items, function(item, idx) {
item.position = idx + 1;
});
})
however I'd really question the need to do so. The item's position in the array provides all the information you need.
Demo ~ http://plnkr.co/edit/H5JbsABEdceSXENFFBBO?p=preview

using ng-click to update every cell in a table column

I'm new to Angular and I was trying to figure out how to have the data in one of the columns in a table I made convert from one number format to another when the user clicks on the top of the table cell. I've create a filter already, but i don't know how to call it so it effects all the cells in the table.
<tr ng-repeat="x in data">
<td>{{x.id}}</td>
<td>{{x.name}} </td>
<td>{{x.desc}}></td>
<td>{{x.number}}</td> <--- THIS IS WHAT I WANT TO CONVERT
</tr>
I'm not even sure where to start with this. I basically have a ng-click directive call "convert" which I've defined in the controller. I know that if I define a variable in the $scope (such as $scope.foo = "1") and then call the convert() function I can replace the value like this:
$scope.convert = function(){
$scope.foo = 2;
}
And then my table updates with that value. But what if every table cell in that column has a different value, and I basically want to run that value through a filter I've made.
Any suggestions on how to approach this?
You said you already have a filter?
Then just give your filter an argument 'numberFormat':
angular.
module('yourModule').
filter('yourFilter', function() {
return function(input, numberFormat) {
// convert the input according to the numberFormat
return filteredValue;
};
});
Then you can update the format in your convert() scope-method:
$scope.convert = function(){
$scope.numberFormat = 'long';
}
and pass it to your filter:
<td>{{x.number | yourFilter:numberFormat}}</td>
BTW:
Read about controllerAs - IMHO it is a better practice to store values at the controller rrather than directly on the scope.
Your function simply needs to update number property of the each object in the data array. You could do it like this:
$scope.convert = function() {
$scope.data.forEach(function(item) {
item.number = item.number + 2; // Convert number somehow
});
};
I assume click handler on table header of column as below
<th ng-click="convert()">column of number</th>
in the controller write the function as below
$scope.convert = function() {
$scope.data.forEach(function(obj) {
obj.number += 1;
})
//$scope.$apply() use this is template is not refreshed
}

AngularJS - Pass $index or the item itself via ng-click?

Say I have
<div ng-repeat="item in scope.allItems">
<div ng-click="doSomething(...)"></div>
</div>
If I want to do something with item upon click, which one is a better option?
Option 1
Use ng-click="doSomething($index) and have:
$scope.doSomething = function($index) {
var myItem = $scope.allItems[$index];
}
Option 2
Use ng-click="doSomething(item) and have:
$scope.doSomething = function(myItem) {
// Do whatever
}
If you are just doing something to the item, pass that in. This way the function doesn't need to know which array the item belongs to:
$scope.addColor = function(car) {
car.color = 'red';
};
If, on the other hand, you need to modify the array I prefer to pass in $index and save having to loop through the array looking for a match:
$scope.deleteCar = function(index, cars) {
cars.splice(index, 1);
};
If you want use filter for scope.allItems, use Option 2 - because using filters change element $index.
If you don't use filter,you can use Option 1.
IMHO Option 2 more easy and useful than Option 1, so i already use Option 2.

In an ng-repeat, is the iterator offset of $index dynamic to the results of a filter

In an ng-repeat, is the iterator offset of $index dynamic to what is visible? I am getting seemingly incorrect $index values when a filter is applied.
Working with no filter applied:
Not appearing to work with filter applied (Note the console log):
When a filter is removed:
And finally my ng-click call:
<a ng-click="showHideOrderDropDown($index)" href="">
Show More<br/><i class="icon-arrow-down"></i>
</a>
Click handler:
$scope.showHideOrderDropDown = function(index) {
console.log(index);
$scope.data[index].orderDropDown = !$scope.data[index].orderDropDown;
};
Now I can easily work around this, but I was just hoping for some clarification.
After doing some research it appears that applying a Filter actually adds and removes (not hides) elements from the ng-repeat therefore the $index would apply to the new order of the array and no longer reflect the $scope array object.
Since asking the question I went ahead and passed the database id to the controller instead.
$scope.showHideOrderDropDown = function(id) {
for (var i = 0; i < $scope.data.length; i++) {
if ($scope.data[i].id === id) {
$scope.data[i].orderDropDown = !$scope.data[i].orderDropDown;
}
}
};

Resources